From 4b450e2e5cdbb655e1c18a0292d4cdc5fc07e62d Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:39:17 +0900 Subject: [PATCH 01/24] =?UTF-8?q?WIP=20=EC=8B=9C=ED=97=98:=20Tor=20=C2=B7?= =?UTF-8?q?=20CEF=20=C2=B7=20DNS=20=EC=8B=A4=EC=A0=9C=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20=EC=8B=9C=ED=97=98=EC=9D=98=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=8A=94=20heldByApp=20(tor=20=C2=B7=20WebVi?= =?UTF-8?q?ew=20=EA=B0=80=20=EC=95=B1=EC=9D=B4=20=EB=81=9D=EB=82=A0=20?= =?UTF-8?q?=EB=95=8C=EA=B9=8C=EC=A7=80=20=EC=9E=A1=EC=95=84=20%TEMP%=20?= =?UTF-8?q?=EC=97=90=20jj=5Ftor=5Freal=5F=20=EC=9D=B4=20=EB=82=A8=EC=95=98?= =?UTF-8?q?=EC=9D=8C=20-=20test=5Ftemp=5Fcheck=20=EA=B0=80=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=ED=8F=B4=EB=8D=94=EC=99=80=20=ED=95=A8=EA=BB=98=20?= =?UTF-8?q?=EC=A7=80=EC=9B=80,=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80?= =?UTF-8?q?=EC=8B=9C=2010/20)=20(=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 --- integration_test/cef_dns_real_test.dart | 2 +- integration_test/dns_block_real_test.dart | 2 +- integration_test/tor_dns_real_test.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integration_test/cef_dns_real_test.dart b/integration_test/cef_dns_real_test.dart index e5f74d2..b46952c 100644 --- a/integration_test/cef_dns_real_test.dart +++ b/integration_test/cef_dns_real_test.dart @@ -34,7 +34,7 @@ void main() { } CefRuntime.rememberStartState(); expect(CefRuntime.readyThisRun, isTrue); - final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? testTempDir('jj_cef_real_').path; + final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? testTempDir('jj_cef_real_', heldByApp: true).path; Directory(data).createSync(recursive: true); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); c.settings diff --git a/integration_test/dns_block_real_test.dart b/integration_test/dns_block_real_test.dart index f38fd6b..61bfe2e 100644 --- a/integration_test/dns_block_real_test.dart +++ b/integration_test/dns_block_real_test.dart @@ -21,7 +21,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('250: 닿지 않는 DNS → 브라우저에 앱 덮개 (Edge 실제)', (tester) async { - final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? testTempDir('jj_dns_block_').path; + final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? testTempDir('jj_dns_block_', heldByApp: true).path; Directory(data).createSync(recursive: true); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); c.settings diff --git a/integration_test/tor_dns_real_test.dart b/integration_test/tor_dns_real_test.dart index 7570d25..ab3ee8b 100644 --- a/integration_test/tor_dns_real_test.dart +++ b/integration_test/tor_dns_real_test.dart @@ -27,7 +27,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('DNS 강제 · Tor 화면 실제 연결', (tester) async { - final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? testTempDir('jj_tor_real_').path; + final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? testTempDir('jj_tor_real_', heldByApp: true).path; Directory(data).createSync(recursive: true); ComponentStore.dataDirectory = data; ComponentStore.shared = ComponentStore(data); 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 02/24] =?UTF-8?q?WIP=20Android=20HWP=20=C2=B7=20HWPX=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=201=EB=8B=A8=EA=B3=84=20(=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20):=20rhwp=20v0.8.7=20(MIT?= =?UTF-8?q?,=20=EC=BB=A4=EB=B0=8B=201a76570=20=EA=B3=A0=EC=A0=95)=20?= =?UTF-8?q?=EB=A5=BC=20=EC=96=87=EC=9D=80=20C=20ABI=20(tool/rhwp=5Fffi)=20?= =?UTF-8?q?=EC=99=80=20=ED=95=A8=EA=BB=98=20NDK=20=EB=A1=9C=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20(tool/build=5Frhwp=5Fandroid.sh=20-=20git=20sparse?= =?UTF-8?q?=20=EB=B0=9B=EA=B8=B0=20=C2=B7=20Rust=201.93.1=20=C2=B7=20Cargo?= =?UTF-8?q?.lock=20--locked=20=C2=B7=2016KB=20=EC=A0=95=EB=A0=AC=20=C2=B7?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20/jj=20=EB=A1=9C=20=EB=B0=94=EA=BF=88)?= =?UTF-8?q?=20=E2=86=92=20APK=20=EC=9D=98=20libjj=5Frhwp.so=20=EB=A5=BC=20?= =?UTF-8?q?dart:ffi=20=EB=A1=9C=20=C2=B7=20=EB=B3=B4=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20HwpSource:=20=EB=B3=B4=EB=8A=94=20=EC=AA=BD?= =?UTF-8?q?=EC=9D=98=205=EC=AA=BD=20=EB=AC=B6=EC=9D=8C=EB=A7=8C=20PDF=20?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BE=B8=EA=B3=A0=20=EB=8B=A4=EC=9D=8C=20?= =?UTF-8?q?=EB=AC=B6=EC=9D=8C=EC=9D=84=20=EB=AF=B8=EB=A6=AC,=20=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=EB=A7=88=EB=8B=A4=203=EB=B6=84=20=EC=A0=9C=ED=95=9C,?= =?UTF-8?q?=20=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) }); + } +} From 65098287e87fcb4add67aaf65d10b6625c3655e4 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:34:31 +0900 Subject: [PATCH 03/24] =?UTF-8?q?=EC=8B=9C=ED=97=98:=20remote=5Fidle=5Ftes?= =?UTF-8?q?t=20=ED=9D=94=EB=93=A4=EB=A6=BC=20=EA=B3=A0=EC=B9=A8=EC=9D=84?= =?UTF-8?q?=20rc=5Fnext=20f43f067=20=EC=97=90=EC=84=9C=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=B4=20(slow()=200.1=EC=B4=88=20=EA=B0=84?= =?UTF-8?q?=EA=B2=A9=20=C2=B7=203=EC=B4=88=20=EC=A0=9C=ED=95=9C,=20SFTP=20?= =?UTF-8?q?=EC=A4=80=EB=B9=84=20=EB=8B=A8=EA=B3=84=2010=EC=B4=88=20-=2015,?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD=20=EC=97=86=EC=9D=8C?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- test/remote_idle_test.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/remote_idle_test.dart b/test/remote_idle_test.dart index 2413193..b82a876 100644 --- a/test/remote_idle_test.dart +++ b/test/remote_idle_test.dart @@ -154,15 +154,15 @@ void main() { expect(t, lessThan(const Duration(seconds: 5))); expect(cut, isTrue); await c.close(); - // 0.4초마다 조각 (정한 1초 안) - 끝까지 + // 0.1초마다 조각 (정한 3초 안 - 전체 시험의 부하에서도 넉넉하게, 0b 지적 10/20: 0.4초 대 1초는 한 번 넘음) - 끝까지 Stream> slow() async* { for (var i = 0; i < 5; i++) { - await Future.delayed(const Duration(milliseconds: 400)); + await Future.delayed(const Duration(milliseconds: 100)); yield [i]; } } - expect(await idleRead(slow()).length, 5); + expect(await idleRead(slow(), limit: const Duration(seconds: 3)).length, 5); }); test('도우미: 올리기 - 보내는 쪽이 더 가져가지 않으면 · 다 가져간 뒤 답이 없으면 실패', () async { @@ -297,8 +297,11 @@ void main() { test('받는 중에 서버가 멈추면 · 명령 (열기) 에 답이 없으면 정한 시간 뒤 실패', () async { final c = client(); addTearDown(c.close); + // 준비 (연결 · 목록 · 열기) 는 넉넉하게 - 함께 도는 시험의 부하에서 paramiko 의 답이 1초를 넘은 적이 있다 (15 검증 10/20) + RemoteIdle.limit = const Duration(seconds: 10); expect((await c.list('/')).map((e) => e.rel), contains('/a.bin')); final s = await c.openReadFrom('a.bin'); // 열기까지는 됨 + RemoteIdle.limit = const Duration(seconds: 1); // 받기 (listen) 때 이 시간으로 proxy!.stall = true; // 이제 서버가 아무것도 보내지 않음 final (t, e) = await _timed(() => s.drain()); expect(e, isA(), reason: '$e'); @@ -306,7 +309,9 @@ void main() { final c2 = client(); addTearDown(c2.close); proxy!.stall = false; + RemoteIdle.limit = const Duration(seconds: 10); await c2.list('/'); + RemoteIdle.limit = const Duration(seconds: 1); proxy!.stall = true; final (t2, e2) = await _timed(() => c2.openReadFrom('a.bin')); expect(e2, isA(), reason: '$e2'); @@ -316,7 +321,9 @@ void main() { test('올리는 중에 서버가 멈추면 정한 시간 뒤 실패', () async { final c = client(); addTearDown(c.close); + RemoteIdle.limit = const Duration(seconds: 10); // 준비는 넉넉하게 (위와 같음) await c.list('/'); + RemoteIdle.limit = const Duration(seconds: 1); proxy!.stall = true; final (t, e) = await _timed(() => c.write('b.bin', _big())); expect(e, isA(), reason: '$e'); From 34efa34193f279a62a8241cf10738273463fcfeb Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:46:30 +0900 Subject: [PATCH 04/24] =?UTF-8?q?WIP=20HWP=202=EB=8B=A8=EA=B3=84=20(?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20):=20?= =?UTF-8?q?=EC=97=B4=EC=96=B4=20=EB=91=90=EB=8A=94=20PDF=20=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=EC=9D=80=20=EC=B5=9C=EA=B7=BC=203=EA=B0=9C=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20(=EB=AA=87=EB=B0=B1=20=EC=AA=BD=EB=8F=84=20?= =?UTF-8?q?=EB=A9=94=EB=AA=A8=EB=A6=AC=EA=B0=80=20=EB=8A=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EA=B2=8C,=20=EA=B7=B8=EB=A6=AC=EB=8A=94=20=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=EC=9D=80=20=EB=8B=AB=EC=A7=80=20=EC=95=8A=EC=9D=8C,?= =?UTF-8?q?=20=EB=8B=AB=EC=9D=80=20=EB=AC=B6=EC=9D=8C=EC=9D=80=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=A7=8C=20=EB=8B=A4=EC=8B=9C=20=EC=97=B4=EA=B8=B0)?= =?UTF-8?q?=20=C2=B7=20=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=EC=9D=98=20?= =?UTF-8?q?"=EC=97=B4=20=EC=88=98=20=EC=97=86=EC=8A=B5=EB=8B=88=EB=8B=A4"?= =?UTF-8?q?=20=EC=97=90=20[=EB=8B=A4=EC=8B=9C=20=EC=8B=9C=EB=8F=84]=20(?= =?UTF-8?q?=EA=B7=B8=20=EC=9E=A5=EC=9D=98=20=EA=B8=B0=EC=96=B5=EC=9D=84=20?= =?UTF-8?q?=EC=A7=80=EC=9A=B0=EA=B3=A0=20=EB=8B=A4=EC=8B=9C=20=EB=B6=80?= =?UTF-8?q?=EB=A6=84=20-=20=EB=AA=A8=EB=93=A0=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EC=9B=90=EB=B3=B8)=20=C2=B7=20=EB=8B=AB=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=20=EB=A7=8C=EB=93=9C=EB=8A=94=20=EC=A4=91=EC=9D=B8=20=EB=AC=B6?= =?UTF-8?q?=EC=9D=8C=EC=9D=84=20=EA=B8=B0=EB=8B=A4=EB=A6=AC=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=8C=20=C2=B7=20=EB=AC=B6=EC=9D=8C=20=EC=97=AC?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=EC=9D=84=20=EB=B0=94=EA=BF=94=20?= =?UTF-8?q?=EB=81=BC=EC=9A=B8=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20(HwpChunk?= =?UTF-8?q?Opener)=20=ED=95=B4=EC=84=9C=20=EB=8B=A8=EC=9C=84=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20hwp=5Fsource=5Funit=5Ftest=20(PDFium=20=EC=97=86?= =?UTF-8?q?=EC=9D=B4=204=EA=B0=9C)=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=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 --- lib/core/hwp_source.dart | 232 +++++++++++++++++++++++---------- lib/ui/reader_page.dart | 17 ++- test/hwp_source_unit_test.dart | 169 ++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 73 deletions(-) create mode 100644 test/hwp_source_unit_test.dart diff --git a/lib/core/hwp_source.dart b/lib/core/hwp_source.dart index 8b5c0cf..f623a7e 100644 --- a/lib/core/hwp_source.dart +++ b/lib/core/hwp_source.dart @@ -34,77 +34,31 @@ class HwpPdfException implements Exception { 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; +/// 바꾼 PDF 한 묶음 (몇 쪽). 기본은 pdfrx ([pdfChunkOpener]) - 시험은 PDFium 없이 가짜로 +abstract class HwpChunk { + int get pageCount; + Future render(int index, {required int maxWidth}); + Future dispose(); +} + +/// PDF 파일을 묶음으로 연다 +typedef HwpChunkOpener = Future Function(String pdfPath); + +/// 기본: pdfrx 로 연다 +Future pdfChunkOpener(String pdfPath) async { + await pdfrxFlutterInitialize(); + return _PdfChunk(await PdfDocument.openFile(pdfPath)); +} + +class _PdfChunk implements HwpChunk { + final PdfDocument doc; + _PdfChunk(this.doc); @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); - } + int get pageCount => doc.pages.length; @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]; + Future render(int index, {required int maxWidth}) async { + final page = doc.pages[index]; final w = maxWidth.toDouble(); final h = w * page.height / page.width; final image = await page.render(fullWidth: w, fullHeight: h, backgroundColor: 0xffffffff); @@ -116,13 +70,149 @@ class HwpSource extends ReaderSource { } } + @override + Future dispose() => doc.dispose(); +} + +/// 관리자 지시 (10/20): HWP · HWPX 를 보기 화면으로 - 큰 문서 (편람 383쪽은 PC 에서도 14분) 를 통째로 바꾸지 않고, +/// 보는 쪽이 든 [chunk] 쪽 묶음만 PDF 로 바꿔 보이면서 다음 묶음을 뒤에서 미리 만든다. 묶음마다 [timeout], +/// 닫으면 (dispose) 더 만들지 않는다. 실패한 묶음은 그 쪽에 이유가 보이고 [다시 시도] 로 다시 만든다 (보기 화면). +/// 2단계 (10/20): 메모리 - 열어 둔 PDF 묶음은 최근 것 [maxOpenChunks] 개까지 (몇백 쪽 문서도 늘지 않게). 닫은 묶음의 파일은 +/// 남겨 다시 볼 때 바꾸지 않고 파일만 연다. 그리고 있는 묶음은 닫지 않는다. +class HwpSource extends ReaderSource { + final String path; + final HwpPdfDoc doc; + final Directory work; + final List fontDirs; + final int chunk; + final Duration timeout; + final int maxOpenChunks; + final HwpChunkOpener opener; + @override + final String title; + + /// 열어 둔 묶음 (넣은 순서 = 최근에 쓴 순서, 앞이 오래된 것) + final _open = >{}; + + /// PDF 파일을 다 만든 묶음 + final _made = {}; + + /// 지금 그리고 있는 묶음 (닫지 않는다) + final _busy = {}; + var _disposed = false; + + HwpSource._(this.path, this.doc, this.work, this.title, + {required this.fontDirs, required this.chunk, required this.timeout, required this.maxOpenChunks, required this.opener}); + + /// 문서를 연다 (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), + int maxOpenChunks = 3, + HwpChunkOpener opener = pdfChunkOpener}) 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, maxOpenChunks: math.max(2, maxOpenChunks), opener: opener); + } + + @override + int get length => doc.pageCount; + @override + String pageName(int i) => '${i + 1} / $length'; + + /// 열어 둔 묶음 번호 (오래된 것부터 - 시험용) + List get openChunks => _open.keys.toList(); + + /// PDF 파일을 만든 묶음 번호 (시험용) + Set get madeChunks => Set.unmodifiable(_made); + + /// [k] 번째 묶음 (실패하면 지워서 다음에 다시) + Future _chunk(int k) { + final had = _open.remove(k); + if (had != null) { + _open[k] = had; // 최근에 쓴 것으로 + return had; + } + late final Future f; + f = _openChunk(k).onError((e, st) { + if (identical(_open[k], f)) _open.remove(k); + Error.throwWithStackTrace(e, st); + }); + _open[k] = f; + _evict(); + return f; + } + + Future _openChunk(int k) async { + if (_disposed) throw StateError(tr('닫힌 문서입니다')); + final out = p.join(work.path, 'p$k.pdf'); + if (!_made.contains(k) || !File(out).existsSync()) { + final from = k * chunk, to = math.min(from + chunk, length); + try { + await doc.exportPdf(from, to, out, fontDirs: fontDirs).timeout(timeout); + } on TimeoutException { + throw HwpPdfException(trf('{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다', [from + 1, to, timeout.inSeconds])); + } catch (_) { + // 닫은 뒤에 끝난 변환은 (임시 폴더를 이미 지워) 실패한다 - 그 이유 대신 "닫힌 문서" + if (_disposed) throw StateError(tr('닫힌 문서입니다')); + rethrow; + } + _made.add(k); + } + if (_disposed) throw StateError(tr('닫힌 문서입니다')); + final c = await opener(out); + if (_disposed) { + await c.dispose(); + throw StateError(tr('닫힌 문서입니다')); + } + return c; + } + + /// 열어 둔 묶음이 [maxOpenChunks] 를 넘으면 오래된 것부터 닫는다 (그리고 있는 것은 빼고) + void _evict() { + while (_open.length > maxOpenChunks) { + final victim = _open.keys.where((k) => (_busy[k] ?? 0) == 0).firstOrNull; + if (victim == null) return; + final f = _open.remove(victim)!; + unawaited(f.then((c) => c.dispose(), onError: (Object _) {})); + } + } + + @override + Future load(int i, {required int maxWidth}) async { + final k = i ~/ chunk; + _busy[k] = (_busy[k] ?? 0) + 1; + try { + final c = await _chunk(k); + // 다음 묶음을 미리 (넘겨 볼 때 기다리지 않게) - 실패해도 여기서는 알리지 않는다 (그 쪽을 볼 때 보임) + if ((k + 1) * chunk < length) unawaited(_chunk(k + 1).then((_) {}, onError: (Object _) {})); + return await c.render(i - k * chunk, maxWidth: maxWidth); + } finally { + final n = (_busy[k] ?? 1) - 1; + if (n <= 0) { + _busy.remove(k); + } else { + _busy[k] = n; + } + if (!_disposed) _evict(); + } + } + @override Future dispose() async { _disposed = true; - for (final d in _opened) { - await d.dispose(); + final open = [..._open.values]; + _open.clear(); + // 만드는 중인 묶음은 기다리지 않는다 (몇 분 걸릴 수 있음) - 끝나면 닫힌다 ([_openChunk] 가 _disposed 를 본다) + for (final f in open) { + unawaited(f.then((c) => c.dispose(), onError: (Object _) {})); } - _opened.clear(); await doc.close(); // 바꾸는 중이면 그것이 끝난 뒤에 닫힌다 (엔진이 한 번에 하나씩) try { if (work.existsSync()) work.deleteSync(recursive: true); diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 71c12c0..f21251d 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -441,10 +441,23 @@ class _ReaderPageState extends State { final fit = c.settings.readerFit; final slot = _v[i]; final half = slot.part >= 0; + // 관리자 지시 (10/20, HWP 2단계): 이유와 [다시 시도] - 그 장의 기억을 지우고 다시 부른다 (시간 제한을 넘긴 HWP 묶음 등) Widget failed(Object? e) => SizedBox( width: 320, - height: 120, - child: Center(child: Text(trf('열 수 없습니다: {0}', [e]), style: const TextStyle(color: Colors.white70)))); + height: 160, + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text(trf('열 수 없습니다: {0}', [e]), textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70)), + const SizedBox(height: 8), + OutlinedButton( + onPressed: () => setState(() { + _cache.remove(slot.page); + _cacheHalf.remove(slot.page); + _sharp.remove(i); + _sharpWidth.remove(i); + }), + child: Text(tr('다시 시도')), + ), + ])); // 확대해 다시 그린 것이 있으면 그것 (다 그릴 때까지는 앞의 그림을 그대로 보인다) final page = FutureBuilder( key: ValueKey('$_version:${slot.page}:${slot.part}'), diff --git a/test/hwp_source_unit_test.dart b/test/hwp_source_unit_test.dart new file mode 100644 index 0000000..b137ecf --- /dev/null +++ b/test/hwp_source_unit_test.dart @@ -0,0 +1,169 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_test/flutter_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; + +/// 쪽 범위를 글로 적은 파일을 "PDF" 로 만드는 가짜 엔진 (rhwp 대신) - 부른 범위를 적어 둔다 +class _Engine implements HwpPdfEngine { + final int pages; + final calls = <(int, int)>[]; + Completer? hold; + var closed = false; + _Engine(this.pages); + + @override + Future open(String path, {String? password}) async => _Doc(this); +} + +class _Doc implements HwpPdfDoc { + final _Engine e; + _Doc(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)); + final h = e.hold; + if (h != null) await h.future; + File(out).writeAsStringSync('$from-$to'); + } + + @override + Future close() async => e.closed = true; +} + +/// 파일을 읽어 쪽 수를 아는 가짜 묶음 (PDFium 없이) - 연 것 · 닫은 것을 적어 둔다 +class _Opener { + final opened = []; + final disposed = []; + Completer? renderHold; + + Future call(String path) async { + opened.add(p.basename(path)); + final parts = File(path).readAsStringSync().split('-').map(int.parse).toList(); + return _Chunk(this, p.basename(path), parts[1] - parts[0]); + } +} + +class _Chunk implements HwpChunk { + final _Opener o; + final String name; + @override + final int pageCount; + _Chunk(this.o, this.name, this.pageCount); + + @override + Future render(int index, {required int maxWidth}) async { + expect(index, lessThan(pageCount)); + final h = o.renderHold; + if (h != null) await h.future; + return const ReaderImage(); + } + + @override + Future dispose() async => o.disposed.add(name); +} + +/// 관리자 지시 (10/20, HWP 2단계): 보기 원본 HwpSource 의 흐름 - 묶음 나누기 · 미리 만들기 · 열어 두는 묶음 상한 (메모리) · +/// 그리는 묶음은 닫지 않음 · 닫은 묶음은 다시 바꾸지 않고 파일만 · 시간 제한 → 이유 · 다시 시도 · 닫기 +void main() { + late Directory dir; + late File doc; + setUp(() { + dir = Directory.systemTemp.createTempSync('jj_hwpunit_'); + doc = File(p.join(dir.path, 'a.hwp'))..writeAsStringSync('x'); + }); + tearDown(() { + if (dir.existsSync()) dir.deleteSync(recursive: true); + }); + + Future settle() => Future.delayed(const Duration(milliseconds: 50)); + + test('보는 쪽의 묶음 + 다음 묶음만 · 열어 둔 묶음은 3개까지 (오래된 것부터 닫음) · 다시 보면 파일만 연다', () async { + final e = _Engine(40), o = _Opener(); + final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, maxOpenChunks: 3, opener: o.call); + expect(s.length, 40); + await s.load(0, maxWidth: 100); + await settle(); + expect(e.calls, [(0, 5), (5, 10)]); + expect(s.openChunks, [0, 1]); + await s.load(12, maxWidth: 100); // 묶음 2 + 미리 3 → 4개 → 0 을 닫음 + await settle(); + expect(e.calls, [(0, 5), (5, 10), (10, 15), (15, 20)]); + expect(s.openChunks, [1, 2, 3]); + expect(o.disposed, ['p0.pdf']); + // 닫은 묶음 0 을 다시: 바꾸지 않고 (calls 그대로) 파일만 연다 + await s.load(1, maxWidth: 100); + await settle(); + expect(e.calls.where((c) => c == (0, 5)), hasLength(1)); + expect(o.opened.where((n) => n == 'p0.pdf'), hasLength(2)); + expect(s.openChunks.length, lessThanOrEqualTo(3)); + expect(s.madeChunks, containsAll([0, 1, 2, 3])); + // 끝 묶음은 남은 쪽만 + await s.load(39, maxWidth: 100); + expect(e.calls.last, (35, 40)); + await s.dispose(); + expect(e.closed, isTrue); + expect(s.work.existsSync(), isFalse, reason: '닫으면 만든 PDF 들을 지움'); + }); + + test('그리고 있는 묶음은 상한을 넘어도 닫지 않고, 다 그린 뒤에 닫는다', () async { + final e = _Engine(40), o = _Opener(); + final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, maxOpenChunks: 2, opener: o.call); + final hold = Completer(); + o.renderHold = hold; + final drawing = s.load(2, maxWidth: 100); // 묶음 0 을 그리는 중 + await settle(); + o.renderHold = null; + await s.load(20, maxWidth: 100); // 묶음 4 (+ 5) + await s.load(30, maxWidth: 100); // 묶음 6 (+ 7) + await settle(); + expect(o.disposed, isNot(contains('p0.pdf')), reason: '그리는 중'); + expect(s.openChunks, contains(0)); + hold.complete(); + await drawing; + await s.load(35, maxWidth: 100); + await settle(); + expect(o.disposed, contains('p0.pdf'), reason: '다 그린 뒤에는 상한에 맞춰 닫힘'); + expect(s.openChunks.length, lessThanOrEqualTo(2)); + await s.dispose(); + }); + + test('시간 제한을 넘기면 쪽 범위 · 초가 든 이유, 다시 부르면 다시 만든다', () async { + final e = _Engine(8)..hold = Completer(); + final o = _Opener(); + final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, timeout: const Duration(milliseconds: 200), + opener: o.call); + Object? err; + try { + await s.load(0, maxWidth: 100); + } catch (x) { + err = x; + } + expect(err, isA()); + expect('$err', allOf(contains('1~5'), contains('0초'))); + expect(s.openChunks, isNot(contains(0)), reason: '실패한 묶음은 기억하지 않음 - [다시 시도] 가 다시 만든다'); + e.hold!.complete(); + e.hold = null; + await s.load(0, maxWidth: 100); + expect(e.calls.where((c) => c == (0, 5)).length, greaterThanOrEqualTo(2)); + await s.dispose(); + }); + + test('닫으면 만드는 중인 묶음은 끝난 뒤 스스로 닫히고 더 만들지 않는다', () async { + final e = _Engine(10)..hold = Completer(); + final o = _Opener(); + final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, opener: o.call); + final pending = s.load(0, maxWidth: 100).then((_) => null, onError: (Object x) => x); + await settle(); + await s.dispose(); // 기다리지 않고 바로 끝난다 + e.hold!.complete(); + final r = await pending; + expect(r, isA(), reason: '닫힌 문서'); + expect(o.opened, isEmpty, reason: '닫은 뒤에는 PDF 를 열지 않음'); + }); +} From c43637b9cc745b131fbc9be29c4c272c063a5fc0 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:51:04 +0900 Subject: [PATCH 05/24] =?UTF-8?q?WIP=20HWP=20=EA=B3=A0=EC=B9=A8=20(?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20,=2028?= =?UTF-8?q?=20=EC=A7=80=EC=A0=81):=20=EC=A7=84=ED=96=89=20=EC=B0=BD=20(?= =?UTF-8?q?=EC=97=AC=EB=8A=94=20=EC=A4=91=20=C2=B7=20PDF=20=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=94=EA=BE=B8=EB=8A=94=20=EC=A4=91)=20=EC=97=90=20[?= =?UTF-8?q?=EC=B7=A8=EC=86=8C]=20-=20=E2=86=90=20=C2=B7=20[=EC=B7=A8?= =?UTF-8?q?=EC=86=8C]=20=EB=A1=9C=20=EB=A8=BC=EC=A0=80=20=EB=8B=AB?= =?UTF-8?q?=ED=98=94=EC=9C=BC=EB=A9=B4=20=EB=81=9D=EB=82=9C=20=EB=92=A4=20?= =?UTF-8?q?pop=20=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=8C=20(=EC=98=88?= =?UTF-8?q?=EC=A0=84=EC=97=90=EB=8A=94=20=EB=8B=A4=EB=A5=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9D=84=20=EB=8B=AB=EC=9D=84=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EC=97=88=EC=9D=8C,=20Windows=20=EB=AC=B8=EC=84=9C=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=EB=8F=84)=20=C2=B7=20=EA=B7=B8?= =?UTF-8?q?=EB=A7=8C=EB=91=90=EB=A9=B4=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EC=A7=80=20=EC=95=8A=EA=B3=A0=20=EC=97=B0=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=EB=A5=BC=20=EB=8B=AB=EC=9D=8C=20=C2=B7=20?= =?UTF-8?q?=EC=95=B1=EC=9D=B4=20=EC=BC=9C=EC=A7=88=20=EB=95=8C=20=EB=82=A8?= =?UTF-8?q?=EC=9D=80=20HWP=20=EC=9E=91=EC=97=85=20=ED=8F=B4=EB=8D=94=20(jj?= =?UTF-8?q?=5Fhwp=5F)=20=EC=A0=95=EB=A6=AC=20=C2=B7=20=EC=8B=9C=ED=97=98?= =?UTF-8?q?=20progress=5Fdialog=5Ftest=20=C2=B7=20=EC=A0=95=EB=A6=AC=20?= =?UTF-8?q?=EC=8B=9C=ED=97=98=20(=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 --- assets/l10n/en.json | 1 + assets/l10n/ja.json | 1 + assets/l10n/zh-Hans.json | 1 + lib/app/app_controller.dart | 6 ++++ lib/core/hwp_source.dart | 19 +++++++++++ lib/ui/explorer_page.dart | 40 ++++++++--------------- lib/ui/progress_dialog.dart | 41 +++++++++++++++++++++++ test/hwp_source_unit_test.dart | 15 +++++++++ test/progress_dialog_test.dart | 59 ++++++++++++++++++++++++++++++++++ 9 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 lib/ui/progress_dialog.dart create mode 100644 test/progress_dialog_test.dart diff --git a/assets/l10n/en.json b/assets/l10n/en.json index de42852..9c006b7 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -2452,6 +2452,7 @@ "지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "The last run did not end normally (last check {0}:{1}). It has been noted in the job log.", "지난 업데이트가 남긴 임시 파일을 지움: {0}": "Removed temporary files left by the last update: {0}", "지난 작업 기록": "Old job logs", + "지난번에 남은 문서 보기 임시 폴더를 지움: {0}개": "Removed {0} leftover document-view temp folders", "지문 확인하고 연결": "Check fingerprint and connect", "지우고 맞추기": "Delete and sync", "지우고 초기화": "Delete and reset", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 09393b3..53ff40b 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -2452,6 +2452,7 @@ "지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "前回の実行が正常に終了しませんでした(最終確認 {0}:{1})。作業記録に残しました。", "지난 업데이트가 남긴 임시 파일을 지움: {0}": "前回のアップデートが残した一時ファイルを削除: {0}", "지난 작업 기록": "過去の作業記録", + "지난번에 남은 문서 보기 임시 폴더를 지움: {0}개": "前回残った文書表示の一時フォルダーを削除: {0} 個", "지문 확인하고 연결": "フィンガープリントを確認して接続", "지우고 맞추기": "削除して同期", "지우고 초기화": "削除して初期化", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index bd1f8ff..33e369f 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -2452,6 +2452,7 @@ "지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "上次运行未正常结束(最后检查 {0}:{1})。已记录在任务记录中。", "지난 업데이트가 남긴 임시 파일을 지움: {0}": "已删除上次更新留下的临时文件: {0}", "지난 작업 기록": "旧任务记录", + "지난번에 남은 문서 보기 임시 폴더를 지움: {0}개": "已删除上次留下的文档查看临时文件夹: {0} 个", "지문 확인하고 연결": "确认指纹并连接", "지우고 맞추기": "删除并同步", "지우고 초기화": "删除并重置", diff --git a/lib/app/app_controller.dart b/lib/app/app_controller.dart index ccf8268..89a9bcb 100644 --- a/lib/app/app_controller.dart +++ b/lib/app/app_controller.dart @@ -32,6 +32,7 @@ import '../services/platform_services.dart'; import '../services/system_usage.dart'; import 'settings.dart'; import 'cleanup.dart' show removeUpdateLeftovers; +import '../core/hwp_source.dart' show removeStaleHwpWork; import '../l10n/tr.dart'; import '../core/remote_idle.dart'; @@ -213,6 +214,11 @@ class AppController extends ChangeNotifier { final gone = removeUpdateLeftovers(Directory.systemTemp.path); if (gone.isNotEmpty) _log(trf('지난 업데이트가 남긴 임시 파일을 지움: {0}', [gone.join(', ')])); } + // 관리자 지시 (10/20): 강제로 끝나 남은 HWP 보기 작업 폴더 (바꾼 PDF 들) + try { + final hwp = removeStaleHwpWork(await services.storage.tempDirectory()); + if (hwp.isNotEmpty) _log(trf('지난번에 남은 문서 보기 임시 폴더를 지움: {0}개', [hwp.length])); + } catch (_) {} ffmpegVersion = await _tool.version(); if (ffmpegVersion == null) { _log(tr('⚠ FFmpeg 를 찾을 수 없습니다. MKV 만들기를 사용할 수 없습니다.')); diff --git a/lib/core/hwp_source.dart b/lib/core/hwp_source.dart index f623a7e..680dd57 100644 --- a/lib/core/hwp_source.dart +++ b/lib/core/hwp_source.dart @@ -12,6 +12,25 @@ import '../l10n/tr.dart'; /// HWP · HWPX 확장자 (앱 안에서 PDF 로 바꿔 보기 - Android, rhwp) const hwpExtensions = ['hwp', 'hwpx']; +/// 관리자 지시 (10/20): 앱이 강제로 끝나 남은 HWP 작업 폴더 (앱 임시 폴더의 jj_hwp_…, 바꾼 PDF 들) 를 앱이 켜질 때 지운다. +/// 막 생긴 것 ([minAge] 안) 은 남긴다. 지운 이름을 돌려준다 +List removeStaleHwpWork(String tempDir, {Duration minAge = const Duration(minutes: 1), DateTime? now}) { + final before = (now ?? DateTime.now()).subtract(minAge); + final removed = []; + try { + for (final e in Directory(tempDir).listSync(followLinks: false)) { + final name = p.basename(e.path); + if (e is! Directory || !name.startsWith('jj_hwp_')) continue; + try { + if (!e.statSync().modified.isBefore(before)) continue; + e.deleteSync(recursive: true); + removed.add(name); + } catch (_) {} + } + } catch (_) {} + return removed; +} + /// HWP · HWPX → PDF 엔진 (Android: APK 의 libjj_rhwp.so - lib/platform/android/hwp_pdf_ffi.dart, 시험: 가짜) abstract class HwpPdfEngine { /// 문서 열기 (암호가 있으면 [password]). 실패하면 [HwpPdfException] diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 9e616d2..b6d59f9 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -41,6 +41,7 @@ import '../platform/windows/recycle_bin.dart'; import '../core/rsync_filter.dart'; import '../core/sync_preview.dart'; import 'path_label.dart'; +import 'progress_dialog.dart'; import 'sync_preview_view.dart'; import 'sftp_settings.dart'; import 'ftp_settings.dart'; @@ -1768,17 +1769,7 @@ class _ExplorerPageState extends State { Future _viewDoc(String path) async { final temp = await c.services.storage.tempDirectory(); if (!mounted) return; - final dialog = showDialog( - context: context, - barrierDismissible: false, - builder: (_) => AlertDialog( - content: Row(children: [ - const CircularProgressIndicator(), - const SizedBox(width: 16), - Expanded(child: Text(trf('PDF 로 바꾸는 중: {0}', [vBasename(path)]))), - ]), - ), - ); + final dialog = showProgressDialog(context, trf('PDF 로 바꾸는 중: {0}', [vBasename(path)])); String? pdf; Object? error; try { @@ -1787,8 +1778,10 @@ class _ExplorerPageState extends State { } catch (e) { error = e; } - if (mounted) Navigator.of(context).pop(); - await dialog; + // 사용자가 [취소] · ← 로 그만두었으면 결과를 보이지 않는다 (바꾸기는 끝까지 돌았음) + if (dialog.cancelled()) return; + dialog.close(); + await dialog.done; if (pdf == null) { _snack(trf('미리 볼 수 없습니다: {0}', [error])); return; @@ -1808,17 +1801,7 @@ class _ExplorerPageState extends State { 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)]))), - ]), - ), - ); + final dialog = showProgressDialog(context, trf('여는 중: {0}', [vBasename(path)])); HwpSource? src; Object? error; try { @@ -1826,8 +1809,13 @@ class _ExplorerPageState extends State { } catch (e) { error = e; } - if (mounted) Navigator.of(context).pop(); - await dialog; + // 사용자가 [취소] · ← 로 그만두었으면 연 문서를 닫고 끝 (다른 앱으로도 열지 않음) + if (dialog.cancelled()) { + await src?.dispose(); + return true; + } + dialog.close(); + await dialog.done; if (src == null) { _snack(trf('앱 안에서 열지 못해 다른 앱으로 엽니다: {0}', [error])); return false; diff --git a/lib/ui/progress_dialog.dart b/lib/ui/progress_dialog.dart new file mode 100644 index 0000000..e16337e --- /dev/null +++ b/lib/ui/progress_dialog.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +import '../l10n/tr.dart'; + +/// 바꾸는 · 여는 동안의 진행 창 ([취소] 있음). 관리자 지시 (10/20, 28 지적): Android ← · [취소] 로 창이 먼저 닫힐 수 있어, +/// 일이 끝난 뒤에는 그 창이 아직 열려 있을 때만 닫는다 (예전에는 끝난 뒤의 pop() 이 다른 화면을 닫을 수 있었다). +/// - [ProgressDialog.cancelled]: 사용자가 [취소] · ← 로 그만두었는지 (그러면 결과를 보이지 않는다) +/// - [ProgressDialog.close]: 일이 끝나 창을 닫는다 (이미 닫혔으면 아무것도 하지 않는다) +class ProgressDialog { + final Future done; + final bool Function() cancelled; + final void Function() close; + const ProgressDialog._(this.done, this.cancelled, this.close); +} + +ProgressDialog showProgressDialog(BuildContext context, String text) { + BuildContext? dctx; + var open = true; + var byUser = true; + final done = showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) { + dctx = ctx; + return AlertDialog( + content: Row(children: [ + const CircularProgressIndicator(), + const SizedBox(width: 16), + Expanded(child: Text(text)), + ]), + actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(tr('취소')))], + ); + }, + ).whenComplete(() => open = false); + return ProgressDialog._(done, () => !open && byUser, () { + if (!open) return; + byUser = false; + final x = dctx; + if (x != null && x.mounted) Navigator.of(x).pop(); + }); +} diff --git a/test/hwp_source_unit_test.dart b/test/hwp_source_unit_test.dart index b137ecf..ccfbe83 100644 --- a/test/hwp_source_unit_test.dart +++ b/test/hwp_source_unit_test.dart @@ -166,4 +166,19 @@ void main() { expect(r, isA(), reason: '닫힌 문서'); expect(o.opened, isEmpty, reason: '닫은 뒤에는 PDF 를 열지 않음'); }); + + test('앱이 켜질 때 남은 HWP 작업 폴더 (jj_hwp_) 만 지운다 - 막 생긴 것 · 다른 이름은 남김', () { + final old = Directory(p.join(dir.path, 'jj_hwp_1'))..createSync(); + File(p.join(old.path, 'p0.pdf')).writeAsStringSync('x'); + final fresh = Directory(p.join(dir.path, 'jj_hwp_2'))..createSync(); + final other = Directory(p.join(dir.path, 'jj_hwpx_keep'))..createSync(); + final file = File(p.join(dir.path, 'jj_hwp_file'))..writeAsStringSync('x'); + // 5분 뒤로 보면 old · fresh 둘 다 1분이 지났다 - fresh 는 지금 기준으로는 남는다 + expect(removeStaleHwpWork(dir.path), isEmpty, reason: '모두 막 생김'); + final gone = removeStaleHwpWork(dir.path, now: DateTime.now().add(const Duration(minutes: 5))); + expect(gone..sort(), ['jj_hwp_1', 'jj_hwp_2']); + expect(old.existsSync() || fresh.existsSync(), isFalse); + expect(other.existsSync(), isTrue, reason: '이름이 jj_hwp_ 로 시작하지 않음 (jj_hwpx_…)'); + expect(file.existsSync(), isTrue, reason: '폴더만'); + }); } diff --git a/test/progress_dialog_test.dart b/test/progress_dialog_test.dart new file mode 100644 index 0000000..0d9b338 --- /dev/null +++ b/test/progress_dialog_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jj_mkvmaker/ui/progress_dialog.dart'; + +/// 관리자 지시 (10/20, 28 지적): 진행 창이 ← · [취소] 로 먼저 닫혀도, 일이 끝난 뒤 close() 가 아래 화면을 닫지 않는다 +void main() { + Future pushPage(WidgetTester tester) async { + late BuildContext page; + await tester.pumpWidget(MaterialApp(home: Builder(builder: (ctx) { + return Scaffold( + body: TextButton( + onPressed: () => Navigator.of(ctx).push(MaterialPageRoute(builder: (c) { + page = c; + return const Scaffold(body: Text('아래 화면')); + })), + child: const Text('열기'), + ), + ); + }))); + await tester.tap(find.text('열기')); + await tester.pumpAndSettle(); + return page; + } + + testWidgets('← 로 닫으면 그만둔 것 - 끝난 뒤 close() 는 아래 화면을 닫지 않는다', (tester) async { + final page = await pushPage(tester); + final d = showProgressDialog(page, '여는 중: a.hwp'); + await tester.pump(); + expect(find.text('여는 중: a.hwp'), findsOneWidget); + expect(d.cancelled(), isFalse); + // Android ← (창을 닫음) + await tester.binding.handlePopRoute(); + await tester.pumpAndSettle(); + expect(find.text('여는 중: a.hwp'), findsNothing); + expect(d.cancelled(), isTrue); + d.close(); // 일이 끝남 - 이미 닫혔으니 아무것도 닫지 않는다 + await tester.pumpAndSettle(); + expect(find.text('아래 화면'), findsOneWidget, reason: '예전에는 이 pop() 이 아래 화면을 닫았다'); + }); + + testWidgets('[취소] 도 그만둔 것, 일이 끝나 close() 하면 창만 닫히고 그만둔 것이 아니다', (tester) async { + final page = await pushPage(tester); + final a = showProgressDialog(page, '바꾸는 중'); + await tester.pump(); + await tester.tap(find.text('취소')); + await tester.pumpAndSettle(); + expect(a.cancelled(), isTrue); + expect(find.text('아래 화면'), findsOneWidget); + + final b = showProgressDialog(page, '바꾸는 중'); + await tester.pump(); + b.close(); + await tester.pumpAndSettle(); + await b.done; + expect(b.cancelled(), isFalse); + expect(find.text('바꾸는 중'), findsNothing); + expect(find.text('아래 화면'), findsOneWidget); + }); +} From d18b3b02ad8ba7ab0c34b9fc260af9901bedb147 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:58:15 +0900 Subject: [PATCH 06/24] =?UTF-8?q?WIP=20HWP=20=EA=B3=A0=EC=B9=A8=20(?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20,=2028?= =?UTF-8?q?=20=EC=A7=80=EC=A0=81):=20=E2=91=A0=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=84=A0=EC=8A=A4=20-=20.so=20=EC=97=90=20=EC=8B=A4=EC=A0=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=81=ED=81=AC=EB=90=98=EB=8A=94=20crate=20167?= =?UTF-8?q?=EA=B0=9C=20(Android=20=EB=8C=80=EC=83=81=20=C2=B7=20=EC=9D=BC?= =?UTF-8?q?=EB=B0=98=20=EC=9D=98=EC=A1=B4=20=C2=B7=20proc-macro=20?= =?UTF-8?q?=EB=BA=8C)=20=EB=A7=88=EB=8B=A4=20=EC=9D=B4=EB=A6=84=20=C2=B7?= =?UTF-8?q?=20=ED=8C=90=20=C2=B7=20=EB=9D=BC=EC=9D=B4=EC=84=A0=EC=8A=A4=20?= =?UTF-8?q?=C2=B7=20=EC=9B=90=EB=AC=B8=EC=9D=84=20tool/licenses/rhwp=5Fcra?= =?UTF-8?q?tes.py=20=EA=B0=80=20RHWP=5FLICENSES.txt=20=EB=A1=9C=20(?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8?= =?UTF-8?q?=EA=B0=80=20=EB=B6=80=EB=A6=84,=20npm=20=EC=AA=BD=20THIRD=5FPAR?= =?UTF-8?q?TY=5FLICENSES.md=20=EB=8A=94=20=EB=BA=8C),=20licenses=5Ftest=20?= =?UTF-8?q?=EA=B0=80=20Cargo.lock=20=EA=B3=BC=20=EA=B2=AC=EC=A4=8C=20?= =?UTF-8?q?=E2=91=A2=20=EC=A7=84=EC=A7=9C=20=EB=A9=88=EC=B6=94=EA=B8=B0=20?= =?UTF-8?q?-=20jj=5Frhwp=5Fcancel=20(AtomicBool,=20=EC=AA=BD=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=ED=99=95=EC=9D=B8),=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=C2=B7=20=EB=8B=AB=EC=9D=84=20=EB=95=8C=20?= =?UTF-8?q?=EB=B6=80=EB=A6=84=20=E2=91=A3=20=EC=98=A4=EB=A5=98=20=EC=A2=85?= =?UTF-8?q?=EB=A5=98=20=EC=BD=94=EB=93=9C=20(JJ=5FRHWP=5FE=5F*)=20?= =?UTF-8?q?=E2=86=92=20=EB=B2=88=EC=97=AD=20=EA=B8=80,=20=EC=9B=90?= =?UTF-8?q?=EB=AC=B8=EC=9D=80=20[=EC=9E=90=EC=84=B8=ED=9E=88]=20=C2=B7=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EA=B8=B0=EB=A1=9D=20(.so=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EB=B9=8C=EB=93=9C=20=EC=A0=84,=20=EA=B2=80?= =?UTF-8?q?=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 --- assets/l10n/en.json | 9 + assets/l10n/ja.json | 9 + assets/l10n/zh-Hans.json | 9 + assets/licenses/RHWP_LICENSES.txt | 11928 +++++++++++++++++++++++- integration_test/hwp_source_test.dart | 13 +- lib/core/hwp_source.dart | 68 +- lib/platform/android/hwp_pdf_ffi.dart | 49 +- lib/ui/explorer_page.dart | 2 +- lib/ui/reader_page.dart | 14 +- test/hwp_source_unit_test.dart | 25 + test/licenses_test.dart | 26 + tool/build_rhwp_android.sh | 8 +- tool/licenses/rhwp_crates.py | 140 + tool/rhwp_ffi/src/lib.rs | 147 +- 14 files changed, 12183 insertions(+), 264 deletions(-) create mode 100644 tool/licenses/rhwp_crates.py diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 9c006b7..389bc20 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -211,6 +211,7 @@ "GitHub 의 최신 버전과 비교해, 새 버전이 있으면 받아서 설치합니다": "Compares with the latest version on GitHub and downloads and installs it if newer", "HTML 파일로 내보내기": "Export to HTML file", "HTML 파일에서 가져오기": "Import from HTML file", + "HWP · HWPX 문서로 읽을 수 없습니다 (깨졌거나 지원하지 않는 형식)": "Can't read it as an HWP / HWPX document (damaged or unsupported format)", "IPv4 · IPv6 주소를 넣으세요 (예: 8.8.4.4)": "Enter an IPv4 or IPv6 address (e.g. 8.8.4.4)", "IPv4 · IPv6 주소를 넣으세요 (예: 8.8.8.8)": "Enter an IPv4 or IPv6 address (e.g. 8.8.8.8)", "JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - {0} downloads", @@ -806,6 +807,7 @@ "그림을 읽을 수 없습니다": "Cannot read the image", "그림을 페이지로 넣기 (이 페이지 뒤)": "Add images as pages (after this page)", "그림이 거의 같고 마지막 단계가 10배쯤 빠릅니다 (받아 두었을 때)": "Almost the same image, and the last step is about 10× faster (when downloaded)", + "그만두었습니다": "Stopped", "그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다": "{0} of them are newer on the receiving side - the newer files will be overwritten with older ones", "그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께": "Including {0} folders - everything inside them too", "글 → 그림 · 그림 → 그림 (기기 안에서, 모델은 환경 설정에서 받기)": "Text → image · image → image (on this device; download models in Settings)", @@ -1295,6 +1297,7 @@ "바꾸기 ({0}개)": "Rename ({0})", "바꾸는 정도 {0}%": "Change amount {0}%", "바꾸면 바로 저장됩니다": "Saved automatically", + "바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "Couldn't save the converted PDF (check storage space)", "바꾼 것이 없습니다 (원본 그대로)": "Nothing changed (original as is)", "바꿀 글": "Replace with", "바꿀 글 (비우면 지움)": "Replace with (empty = remove)", @@ -1397,6 +1400,7 @@ "벵골어": "Bengali", "변경 내용을 저장하지 않고 나갈까요?": "Leave without saving the changes?", "변환": "Convert", + "변환기 안에서 오류가 났습니다": "An error occurred inside the converter", "보관한 설정으로 되살리고 다시 시작": "Restore kept settings and restart", "보관해 둔 설정을 되살렸습니다: {0}": "Restored the kept settings: {0}", "보기": "View", @@ -1756,6 +1760,8 @@ "알리지 않음": "Don't notify", "알림이 JJ_MKVMaker 이름으로 보이고, 누르면 앱이 열립니다 (관리자 권한 없이 지금 사용자에게만)": "Notifications show as JJ_MKVMaker and open the app when clicked (current user only, no admin rights)", "암호 ZIP 으로 묶는 중 · {0}": "Making password ZIP · {0}", + "암호가 걸린 문서입니다": "This document is password-protected", + "암호가 맞지 않습니다": "The password is incorrect", "암호로 묶기": "Make password ZIP", "암호화": "Encryption", "암호화 (TLS) 연결을 맺지 못했습니다: {0}": "Couldn't establish an encrypted (TLS) connection: {0}", @@ -2073,6 +2079,7 @@ "이 위치 (네트워크 드라이브 · 네트워크 공유 · USB 메모리 등) 에는 휴지통이 없어 바로 지워집니다.": "This location (network drive, network share, USB memory etc.) has no Recycle Bin, so items are deleted right away.", "이 이동 버튼 지우기": "Delete this move button", "이 이동을 다시 할 때 이 옵션을 씁니다. 지금 새 옵션으로 실행할까요?": "These options will be used the next time this move runs. Run it now with the new options?", + "이 쪽을 그리지 못했습니다": "Couldn't render this page", "이 파일에 맞는 자막 우선 (영상 해시)": "Prefer subtitles that match this file (video hash)", "이 파일용": "For this file", "이 파일을 열 수 있는 앱이 없습니다.": "No app can open this file.", @@ -2494,6 +2501,7 @@ "진행 패널 보기": "Show progress panel", "집 NAS 처럼 자체 서명 인증서면 [서버 설정 고치기] 에서 \"인증서 확인 안 함\" 을 켜세요.": "If it uses a self-signed certificate (like a home NAS), turn on \"Don't verify certificate\" in [Edit server settings].", "짧을수록 빨리 맞추지만 배터리를 더 씁니다.": "Shorter means faster syncing but more battery use.", + "쪽 범위가 맞지 않습니다": "The page range is invalid", "차례 기다림": "Waiting in queue", "창": "Panes", "창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)": "Icon of the window, taskbar, tray and the top-left button in the app (the program file and desktop shortcut icons stay the same)", @@ -2661,6 +2669,7 @@ "파일만 골랐을 때": "When only files are selected", "파일을 복사하지 못했습니다": "Could not copy the file", "파일을 분석할 수 없습니다: {0}": "Cannot analyze the file: {0}", + "파일을 읽을 수 없습니다": "Can't read the file", "파일을 읽지 못했습니다": "Could not read the file", "파일이 깨졌습니다": "The file is damaged", "파일이 깨졌습니다 · [다시 받기] 를 눌러 주세요": "The file is damaged · tap [Download again]", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 53ff40b..d000624 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -211,6 +211,7 @@ "GitHub 의 최신 버전과 비교해, 새 버전이 있으면 받아서 설치합니다": "GitHub の最新版と比べ、新しい版があればダウンロードしてインストールします", "HTML 파일로 내보내기": "HTML ファイルに書き出す", "HTML 파일에서 가져오기": "HTML ファイルから取り込む", + "HWP · HWPX 문서로 읽을 수 없습니다 (깨졌거나 지원하지 않는 형식)": "HWP・HWPX 文書として読み込めません (破損しているか未対応の形式)", "IPv4 · IPv6 주소를 넣으세요 (예: 8.8.4.4)": "IPv4・IPv6 アドレスを入力してください (例: 8.8.4.4)", "IPv4 · IPv6 주소를 넣으세요 (예: 8.8.8.8)": "IPv4・IPv6 アドレスを入力してください (例: 8.8.8.8)", "JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - ダウンロード {0}個", @@ -806,6 +807,7 @@ "그림을 읽을 수 없습니다": "画像を読み込めません", "그림을 페이지로 넣기 (이 페이지 뒤)": "画像をページとして追加 (このページの後)", "그림이 거의 같고 마지막 단계가 10배쯤 빠릅니다 (받아 두었을 때)": "画像はほぼ同じで、最後の段階が約 10 倍速くなります (ダウンロード済みの場合)", + "그만두었습니다": "中止しました", "그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다": "そのうち {0} 件は受け取る側の方が新しいです - 新しいファイルを古いもので上書きします", "그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께": "うちフォルダー {0} 件 - 中のファイル・フォルダーもすべて一緒に", "글 → 그림 · 그림 → 그림 (기기 안에서, 모델은 환경 설정에서 받기)": "テキスト → 画像 · 画像 → 画像 (端末内で。モデルは設定でダウンロード)", @@ -1295,6 +1297,7 @@ "바꾸기 ({0}개)": "変更 ({0} 件)", "바꾸는 정도 {0}%": "変える度合い {0}%", "바꾸면 바로 저장됩니다": "変更するとすぐ保存されます", + "바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "変換した PDF を保存できませんでした (空き容量を確認してください)", "바꾼 것이 없습니다 (원본 그대로)": "変更なし(元のまま)", "바꿀 글": "置き換える文字", "바꿀 글 (비우면 지움)": "置き換える文字 (空なら削除)", @@ -1397,6 +1400,7 @@ "벵골어": "ベンガル語", "변경 내용을 저장하지 않고 나갈까요?": "変更を保存せずに終了しますか?", "변환": "変換", + "변환기 안에서 오류가 났습니다": "変換エンジン内でエラーが発生しました", "보관한 설정으로 되살리고 다시 시작": "保管した設定で復元して再起動", "보관해 둔 설정을 되살렸습니다: {0}": "保管した設定を復元しました: {0}", "보기": "表示", @@ -1756,6 +1760,8 @@ "알리지 않음": "知らせない", "알림이 JJ_MKVMaker 이름으로 보이고, 누르면 앱이 열립니다 (관리자 권한 없이 지금 사용자에게만)": "通知が JJ_MKVMaker の名前で表示され、クリックするとアプリが開きます (管理者権限なしで現在のユーザーのみ)", "암호 ZIP 으로 묶는 중 · {0}": "パスワード付き ZIP にまとめています · {0}", + "암호가 걸린 문서입니다": "パスワードで保護された文書です", + "암호가 맞지 않습니다": "パスワードが正しくありません", "암호로 묶기": "パスワード付きでまとめる", "암호화": "暗号化", "암호화 (TLS) 연결을 맺지 못했습니다: {0}": "暗号化 (TLS) 接続を確立できませんでした: {0}", @@ -2073,6 +2079,7 @@ "이 위치 (네트워크 드라이브 · 네트워크 공유 · USB 메모리 등) 에는 휴지통이 없어 바로 지워집니다.": "この場所 (ネットワークドライブ・ネットワーク共有・USB メモリなど) にはごみ箱がないため、すぐに削除されます。", "이 이동 버튼 지우기": "この移動ボタンを削除", "이 이동을 다시 할 때 이 옵션을 씁니다. 지금 새 옵션으로 실행할까요?": "この移動を次に行うときにこのオプションを使います。今すぐ新しいオプションで実行しますか?", + "이 쪽을 그리지 못했습니다": "このページを描画できませんでした", "이 파일에 맞는 자막 우선 (영상 해시)": "このファイルに合う字幕を優先(動画ハッシュ)", "이 파일용": "このファイル用", "이 파일을 열 수 있는 앱이 없습니다.": "このファイルを開けるアプリがありません。", @@ -2494,6 +2501,7 @@ "진행 패널 보기": "進行パネルを表示", "집 NAS 처럼 자체 서명 인증서면 [서버 설정 고치기] 에서 \"인증서 확인 안 함\" 을 켜세요.": "自宅 NAS のように自己署名証明書なら、[サーバー設定を修正] で「証明書を検証しない」をオンにしてください。", "짧을수록 빨리 맞추지만 배터리를 더 씁니다.": "短いほど早く同期しますが、バッテリーをより使います。", + "쪽 범위가 맞지 않습니다": "ページ範囲が正しくありません", "차례 기다림": "順番待ち", "창": "ペイン", "창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)": "ウィンドウ・タスクバー・トレイとアプリ内左上のボタンのアイコンです(実行ファイル・デスクトップのショートカットのアイコンはそのまま)", @@ -2661,6 +2669,7 @@ "파일만 골랐을 때": "ファイルだけを選んだとき", "파일을 복사하지 못했습니다": "ファイルをコピーできませんでした", "파일을 분석할 수 없습니다: {0}": "ファイルを分析できません: {0}", + "파일을 읽을 수 없습니다": "ファイルを読み込めません", "파일을 읽지 못했습니다": "ファイルを読み取れませんでした", "파일이 깨졌습니다": "ファイルが壊れています", "파일이 깨졌습니다 · [다시 받기] 를 눌러 주세요": "ファイルが壊れています・[再ダウンロード] を押してください", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 33e369f..bac176b 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -211,6 +211,7 @@ "GitHub 의 최신 버전과 비교해, 새 버전이 있으면 받아서 설치합니다": "与 GitHub 上的最新版本比较,如有新版本则下载并安装", "HTML 파일로 내보내기": "导出为 HTML 文件", "HTML 파일에서 가져오기": "从 HTML 文件导入", + "HWP · HWPX 문서로 읽을 수 없습니다 (깨졌거나 지원하지 않는 형식)": "无法作为 HWP · HWPX 文档读取 (已损坏或格式不受支持)", "IPv4 · IPv6 주소를 넣으세요 (예: 8.8.4.4)": "请输入 IPv4 · IPv6 地址 (例: 8.8.4.4)", "IPv4 · IPv6 주소를 넣으세요 (예: 8.8.8.8)": "请输入 IPv4 · IPv6 地址 (例: 8.8.8.8)", "JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - 下载 {0} 个", @@ -806,6 +807,7 @@ "그림을 읽을 수 없습니다": "无法读取图片", "그림을 페이지로 넣기 (이 페이지 뒤)": "将图片作为页面加入 (本页之后)", "그림이 거의 같고 마지막 단계가 10배쯤 빠릅니다 (받아 두었을 때)": "图片几乎相同,最后一步快约 10 倍 (已下载时)", + "그만두었습니다": "已停止", "그중 {0}개는 받는 쪽이 더 새것입니다 - 더 새 파일을 예전 것으로 덮어씁니다": "其中 {0} 个在接收端较新 - 将用旧文件覆盖较新的文件", "그중 폴더 {0}개 - 안의 파일 · 폴더도 모두 함께": "其中 {0} 个文件夹 - 其中的文件和文件夹也一并处理", "글 → 그림 · 그림 → 그림 (기기 안에서, 모델은 환경 설정에서 받기)": "文字 → 图片 · 图片 → 图片(在本设备上;模型在设置中下载)", @@ -1295,6 +1297,7 @@ "바꾸기 ({0}개)": "重命名 ({0} 个)", "바꾸는 정도 {0}%": "改变程度 {0}%", "바꾸면 바로 저장됩니다": "更改后立即保存", + "바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "无法保存转换后的 PDF (请检查存储空间)", "바꾼 것이 없습니다 (원본 그대로)": "未做更改(保持原样)", "바꿀 글": "替换为", "바꿀 글 (비우면 지움)": "替换为 (留空则删除)", @@ -1397,6 +1400,7 @@ "벵골어": "孟加拉语", "변경 내용을 저장하지 않고 나갈까요?": "不保存更改就离开吗?", "변환": "转换", + "변환기 안에서 오류가 났습니다": "转换器内部出错", "보관한 설정으로 되살리고 다시 시작": "用保存的设置恢复并重新启动", "보관해 둔 설정을 되살렸습니다: {0}": "已恢复保存的设置: {0}", "보기": "查看", @@ -1756,6 +1760,8 @@ "알리지 않음": "不提醒", "알림이 JJ_MKVMaker 이름으로 보이고, 누르면 앱이 열립니다 (관리자 권한 없이 지금 사용자에게만)": "通知以 JJ_MKVMaker 名义显示,点击即可打开应用 (仅当前用户,无需管理员权限)", "암호 ZIP 으로 묶는 중 · {0}": "正在创建加密 ZIP · {0}", + "암호가 걸린 문서입니다": "这是受密码保护的文档", + "암호가 맞지 않습니다": "密码不正确", "암호로 묶기": "加密压缩", "암호화": "加密", "암호화 (TLS) 연결을 맺지 못했습니다: {0}": "无法建立加密 (TLS) 连接:{0}", @@ -2073,6 +2079,7 @@ "이 위치 (네트워크 드라이브 · 네트워크 공유 · USB 메모리 등) 에는 휴지통이 없어 바로 지워집니다.": "此位置 (网络驱动器 · 网络共享 · U 盘等) 没有回收站,会直接删除。", "이 이동 버튼 지우기": "删除此移动按钮", "이 이동을 다시 할 때 이 옵션을 씁니다. 지금 새 옵션으로 실행할까요?": "下次执行此移动时将使用这些选项。现在用新选项执行吗?", + "이 쪽을 그리지 못했습니다": "无法渲染此页", "이 파일에 맞는 자막 우선 (영상 해시)": "优先匹配此文件的字幕(视频哈希)", "이 파일용": "适用于此文件", "이 파일을 열 수 있는 앱이 없습니다.": "没有可以打开此文件的应用。", @@ -2494,6 +2501,7 @@ "진행 패널 보기": "显示进度面板", "집 NAS 처럼 자체 서명 인증서면 [서버 설정 고치기] 에서 \"인증서 확인 안 함\" 을 켜세요.": "如果是自签名证书 (如家用 NAS),请在 [修改服务器设置] 中开启“不验证证书”。", "짧을수록 빨리 맞추지만 배터리를 더 씁니다.": "间隔越短同步越快,但更耗电。", + "쪽 범위가 맞지 않습니다": "页面范围无效", "차례 기다림": "排队等待", "창": "窗格", "창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)": "窗口、任务栏、托盘以及应用内左上角按钮的图标(程序文件和桌面快捷方式的图标不变)", @@ -2661,6 +2669,7 @@ "파일만 골랐을 때": "只选择文件时", "파일을 복사하지 못했습니다": "无法复制文件", "파일을 분석할 수 없습니다: {0}": "无法分析文件: {0}", + "파일을 읽을 수 없습니다": "无法读取文件", "파일을 읽지 못했습니다": "无法读取文件", "파일이 깨졌습니다": "文件已损坏", "파일이 깨졌습니다 · [다시 받기] 를 눌러 주세요": "文件已损坏 · 请点按 [重新下载]", diff --git a/assets/licenses/RHWP_LICENSES.txt b/assets/licenses/RHWP_LICENSES.txt index 2e94b57..78972f8 100644 --- a/assets/licenses/RHWP_LICENSES.txt +++ b/assets/licenses/RHWP_LICENSES.txt @@ -1,6 +1,11 @@ -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 +rhwp v0.8.7 (https://github.com/edwardkim/rhwp, commit 1a76570e833917d15817415a53c09ad61ab3203f) - MIT +Android: libjj_rhwp.so (HWP / HWPX -> PDF), built by tool/build_rhwp_android.sh with tool/rhwp_ffi (MIT) +This file lists every Rust crate linked into libjj_rhwp.so (target aarch64-linux-android, normal dependencies, +build-only / dev / proc-macro crates excluded) with its license and the license / notice files it ships. +--------------------------------------------------------------------- +rhwp LICENSE +--------------------------------------------------------------------- MIT License Copyright (c) 2025-2026 Edward Kim @@ -24,255 +29,11782 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------- -rhwp THIRD_PARTY_LICENSES.md +Linked crates (167) +--------------------------------------------------------------------- +linked: adler2 2.0.1 (0BSD OR MIT OR Apache-2.0) +linked: aead 0.6.1 (MIT OR Apache-2.0) +linked: aes 0.9.3 (MIT OR Apache-2.0) +linked: argon2 0.6.0 (MIT OR Apache-2.0) +linked: arrayref 0.3.9 (BSD-2-Clause) +linked: arrayvec 0.7.8 (MIT OR Apache-2.0) +linked: base64 0.22.1 (MIT OR Apache-2.0) +linked: base64 0.23.1 (MIT OR Apache-2.0) +linked: base64ct 1.8.3 (Apache-2.0 OR MIT) +linked: bitflags 1.3.2 (MIT/Apache-2.0) +linked: bitflags 2.13.1 (MIT OR Apache-2.0) +linked: blake2 0.11.0 (MIT OR Apache-2.0) +linked: blake3 1.8.7 (CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception) +linked: block-buffer 0.12.1 (MIT OR Apache-2.0) +linked: block-padding 0.4.2 (MIT OR Apache-2.0) +linked: bumpalo 3.20.3 (MIT OR Apache-2.0) +linked: bytemuck 1.25.2 (Zlib OR Apache-2.0 OR MIT) +linked: byteorder 1.5.0 (Unlicense OR MIT) +linked: byteorder-lite 0.1.0 (Unlicense OR MIT) +linked: cbc 0.2.1 (MIT OR Apache-2.0) +linked: cfb 0.15.0 (MIT) +linked: cfg-if 1.0.4 (MIT OR Apache-2.0) +linked: chacha20 0.10.1 (MIT OR Apache-2.0) +linked: chacha20poly1305 0.11.0 (Apache-2.0 OR MIT) +linked: cipher 0.5.2 (MIT OR Apache-2.0) +linked: cmov 0.5.4 (Apache-2.0 OR MIT) +linked: codepage 0.1.3 (Apache-2.0 OR MIT) +linked: color_quant 1.1.0 (MIT) +linked: const-oid 0.10.2 (Apache-2.0 OR MIT) +linked: constant_time_eq 0.4.2 (CC0-1.0 OR MIT-0 OR Apache-2.0) +linked: core_maths 0.1.1 (MIT) +linked: cpubits 0.1.1 (MIT OR Apache-2.0) +linked: cpufeatures 0.3.0 (MIT OR Apache-2.0) +linked: crc32fast 1.5.2 (MIT OR Apache-2.0) +linked: crypto-common 0.2.2 (MIT OR Apache-2.0) +linked: ctutils 0.4.2 (Apache-2.0 OR MIT) +linked: curve25519-dalek 5.0.0 (BSD-3-Clause) +linked: data-url 0.3.2 (MIT OR Apache-2.0) +linked: des 0.9.0 (MIT OR Apache-2.0) +linked: digest 0.11.3 (MIT OR Apache-2.0) +linked: ed25519 3.0.0 (Apache-2.0 OR MIT) +linked: ed25519-dalek 3.0.0 (BSD-3-Clause) +linked: embedded-io 0.7.1 (MIT OR Apache-2.0) +linked: encoding_rs 0.8.42 ((Apache-2.0 OR MIT) AND BSD-3-Clause) +linked: equivalent 1.0.2 (Apache-2.0 OR MIT) +linked: fax 0.2.7 (MIT) +linked: fdeflate 0.3.7 (MIT OR Apache-2.0) +linked: flate2 1.1.10 (MIT OR Apache-2.0) +linked: float-cmp 0.9.0 (MIT) +linked: fnv 1.0.7 (Apache-2.0 / MIT) +linked: font-types 0.11.3 (MIT OR Apache-2.0) +linked: fontdb 0.23.0 (MIT) +linked: getrandom 0.4.3 (MIT OR Apache-2.0) +linked: gif 0.13.3 (MIT OR Apache-2.0) +linked: gif 0.14.2 (MIT OR Apache-2.0) +linked: half 2.7.1 (MIT OR Apache-2.0) +linked: hashbrown 0.17.1 (MIT OR Apache-2.0) +linked: hmac 0.13.0 (MIT OR Apache-2.0) +linked: hybrid-array 0.4.14 (MIT OR Apache-2.0) +linked: image 0.25.10 (MIT OR Apache-2.0) +linked: image-webp 0.2.4 (MIT OR Apache-2.0) +linked: imagesize 0.13.0 (MIT) +linked: indexmap 2.14.0 (Apache-2.0 OR MIT) +linked: inout 0.2.2 (MIT OR Apache-2.0) +linked: itoa 1.0.18 (MIT OR Apache-2.0) +linked: keccak 0.2.1 (Apache-2.0 OR MIT) +linked: kem 0.3.0 (Apache-2.0 OR MIT) +linked: kurbo 0.11.3 (Apache-2.0 OR MIT) +linked: kurbo 0.13.1 (Apache-2.0 OR MIT) +linked: libc 0.2.189 (MIT OR Apache-2.0) +linked: libm 0.2.16 (MIT) +linked: log 0.4.34 (MIT OR Apache-2.0) +linked: memchr 2.8.3 (Unlicense OR MIT) +linked: memmap2 0.9.11 (MIT OR Apache-2.0) +linked: miniz_oxide 0.8.9 (MIT OR Zlib OR Apache-2.0) +linked: miniz_oxide 0.9.1 (MIT OR Zlib OR Apache-2.0) +linked: ml-dsa 0.1.1 (Apache-2.0 OR MIT) +linked: ml-kem 0.3.2 (Apache-2.0 OR MIT) +linked: module-lattice 0.2.3 (Apache-2.0 OR MIT) +linked: moxcms 0.8.1 (BSD-3-Clause OR Apache-2.0) +linked: num-traits 0.2.19 (MIT OR Apache-2.0) +linked: once_cell 1.21.4 (MIT OR Apache-2.0) +linked: pbkdf2 0.13.0 (MIT OR Apache-2.0) +linked: pcx 0.2.5 (MIT OR Apache-2.0 OR WTFPL) +linked: pdf-writer 0.12.1 (MIT OR Apache-2.0) +linked: pico-args 0.5.0 (MIT) +linked: png 0.17.16 (MIT OR Apache-2.0) +linked: png 0.18.1 (MIT OR Apache-2.0) +linked: poly1305 0.9.1 (Apache-2.0 OR MIT) +linked: polycool 0.4.0 (MIT OR Apache-2.0) +linked: pxfm 0.1.30 (BSD-3-Clause OR Apache-2.0) +linked: quick-error 2.0.1 (MIT/Apache-2.0) +linked: quick-xml 0.42.0 (MIT) +linked: rand_core 0.10.1 (MIT OR Apache-2.0) +linked: read-fonts 0.39.2 (MIT OR Apache-2.0) +linked: resvg 0.45.1 (Apache-2.0 OR MIT) +linked: rgb 0.8.53 (MIT) +linked: rhwp 0.8.7 (MIT) +linked: rhwp-contracts 0.1.0 (MIT) +linked: rhwp-ooxml-chart 0.1.0 (MIT) +linked: rhwp-password-crypto 0.1.0 (MIT) +linked: roxmltree 0.20.0 (MIT OR Apache-2.0) +linked: roxmltree 0.21.1 (MIT OR Apache-2.0) +linked: rustc-hash 2.1.3 (Apache-2.0 OR MIT) +linked: rustybuzz 0.20.1 (MIT) +linked: ryu 1.0.23 (Apache-2.0 OR BSL-1.0) +linked: scopeguard 1.2.0 (MIT OR Apache-2.0) +linked: serde 1.0.229 (MIT OR Apache-2.0) +linked: serde_core 1.0.229 (MIT OR Apache-2.0) +linked: serde_json 1.0.151 (MIT OR Apache-2.0) +linked: sha1 0.11.0 (MIT OR Apache-2.0) +linked: sha2 0.11.0 (MIT OR Apache-2.0) +linked: sha3 0.11.0 (MIT OR Apache-2.0) +linked: shake 0.1.0 (MIT OR Apache-2.0) +linked: signature 3.0.0 (Apache-2.0 OR MIT) +linked: simd-adler32 0.3.10 (MIT) +linked: simdutf8 0.1.5 (MIT OR Apache-2.0) +linked: simplecss 0.2.2 (Apache-2.0 OR MIT) +linked: siphasher 1.0.3 (MIT/Apache-2.0) +linked: skrifa 0.42.1 (MIT OR Apache-2.0) +linked: slotmap 1.1.1 (Zlib) +linked: smallvec 1.15.2 (MIT OR Apache-2.0) +linked: snafu 0.9.2 (MIT OR Apache-2.0) +linked: sponge-cursor 0.1.0 (MIT OR Apache-2.0) +linked: strict-num 0.1.1 (MIT) +linked: strum 0.28.0 (MIT) +linked: subsetter 0.2.6 (MIT OR Apache-2.0) +linked: subtle 2.6.1 (BSD-3-Clause) +linked: svg2pdf 0.13.0 (MIT OR Apache-2.0) +linked: svgtypes 0.15.3 (Apache-2.0 OR MIT) +linked: svgtypes 0.16.1 (Apache-2.0 OR MIT) +linked: tiff 0.11.3 (MIT) +linked: tiny-skia 0.11.4 (BSD-3-Clause) +linked: tiny-skia-path 0.11.4 (BSD-3-Clause) +linked: tinyvec 1.12.0 (Zlib OR Apache-2.0 OR MIT) +linked: tinyvec_macros 0.1.1 (MIT OR Apache-2.0 OR Zlib) +linked: ttf-parser 0.25.1 (MIT OR Apache-2.0) +linked: typed-path 0.12.3 (MIT OR Apache-2.0) +linked: typenum 1.20.1 (MIT OR Apache-2.0) +linked: unicode-bidi 0.3.18 (MIT OR Apache-2.0) +linked: unicode-bidi-mirroring 0.4.0 (MIT/Apache-2.0) +linked: unicode-ccc 0.4.0 (MIT/Apache-2.0) +linked: unicode-ident 1.0.24 ((MIT OR Apache-2.0) AND Unicode-3.0) +linked: unicode-properties 0.1.4 (MIT/Apache-2.0) +linked: unicode-script 0.5.8 (MIT OR Apache-2.0) +linked: unicode-segmentation 1.13.3 (MIT OR Apache-2.0) +linked: unicode-vo 0.1.0 (MIT/Apache-2.0) +linked: unicode-width 0.2.2 (MIT OR Apache-2.0) +linked: universal-hash 0.6.1 (MIT OR Apache-2.0) +linked: usvg 0.45.1 (Apache-2.0 OR MIT) +linked: uuid 1.24.1 (Apache-2.0 OR MIT) +linked: wasm-bindgen 0.2.127 (MIT OR Apache-2.0) +linked: wasm-bindgen-shared 0.2.127 (MIT OR Apache-2.0) +linked: web-time 1.1.0 (MIT OR Apache-2.0) +linked: weezl 0.1.12 (MIT OR Apache-2.0) +linked: write-fonts 0.48.1 (MIT OR Apache-2.0) +linked: xmlwriter 0.1.0 (MIT) +linked: zerocopy 0.8.56 (BSD-2-Clause OR Apache-2.0 OR MIT) +linked: zeroize 1.9.0 (Apache-2.0 OR MIT) +linked: zip 8.6.0 (MIT) +linked: zlib-rs 0.6.7 (Zlib) +linked: zmij 1.0.23 (MIT) +linked: zopfli 0.8.3 (Apache-2.0) +linked: zune-core 0.4.12 (MIT OR Apache-2.0 OR Zlib) +linked: zune-core 0.5.3 (MIT OR Apache-2.0 OR Zlib) +linked: zune-jpeg 0.4.21 (MIT OR Apache-2.0 OR Zlib) +linked: zune-jpeg 0.5.15 (MIT OR Apache-2.0 OR Zlib) + +--------------------------------------------------------------------- +Not linked into libjj_rhwp.so (build tools, proc-macros, other platforms) - listed for completeness +--------------------------------------------------------------------- +not linked: autocfg 1.5.1 +not linked: bytemuck_derive 1.12.0 +not linked: cc 1.4.3 +not linked: core_detect 1.0.0 +not linked: crunchy 0.2.4 +not linked: curve25519-dalek-derive 0.1.1 +not linked: der 0.8.1 +not linked: euclid 0.22.14 +not linked: fiat-crypto 0.3.0 +not linked: find-msvc-tools 0.1.11 +not linked: fontconfig-parser 0.5.8 +not linked: futures-core 0.3.34 +not linked: futures-task 0.3.34 +not linked: futures-util 0.3.34 +not linked: heck 0.5.0 +not linked: js-sys 0.3.104 +not linked: multiversion_no_op 1.0.0 +not linked: password-hash 0.6.1 +not linked: paste 1.0.15 +not linked: phc 0.6.1 +not linked: pin-project-lite 0.2.17 +not linked: pkcs8 0.11.0 +not linked: proc-macro2 1.0.107 +not linked: quote 1.0.47 +not linked: r-efi 6.0.0 +not linked: rustc_version 0.4.1 +not linked: rustversion 1.0.23 +not linked: semver 1.0.28 +not linked: serde_derive 1.0.229 +not linked: shlex 2.0.1 +not linked: slab 0.4.12 +not linked: snafu-derive 0.9.2 +not linked: spki 0.8.0 +not linked: strum_macros 0.28.0 +not linked: syn 2.0.119 +not linked: syn 3.0.3 +not linked: version_check 0.9.5 +not linked: wasm-bindgen-macro 0.2.127 +not linked: wasm-bindgen-macro-support 0.2.127 +not linked: web-sys 0.3.104 +not linked: zerocopy-derive 0.8.56 + +--------------------------------------------------------------------- +License and notice files --------------------------------------------------------------------- -# Third-Party Licenses +=== adler2 2.0.1 (0BSD OR MIT OR Apache-2.0) === +Authors: Jonas Schievink , oyvindln +https://github.com/oyvindln/adler2 +[LICENSE-0BSD] +Copyright (C) Jonas Schievink -rhwp 프로젝트가 사용하는 서드파티 라이브러리 및 리소스의 라이선스 목록이다. +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. -기준 파일: +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -- `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` +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/LICENSE-2.0 ---- +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -## Rust 크레이트 +1. Definitions. -### 직접 의존성 + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -| 크레이트 | 버전 | 라이선스 | 저장소 | 비고 | -|---------|------|---------|--------|------| -| 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 처리 | + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -### 전체 Rust 의존성 요약 + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -`cargo metadata --locked` 기준 package entry는 247개이며, 그중 registry 또는 Git -source를 가진 외부 Rust 크레이트는 243개다. 나머지 4개는 현재 workspace 패키지다. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -주요 라이선스군: + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -- 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 선택 가능) + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -> 모든 Rust 의존성은 MIT, Apache-2.0, BSD, Zlib, Unlicense, 0BSD, ISC 등 permissive license 계열이거나 -> permissive option을 포함하며, rhwp의 MIT 라이선스와 호환된다. + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). ---- + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. -## npm 패키지 + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." -### npm 배포 패키지 + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -| 패키지 | 버전 | 라이선스 | 용도 | -|--------|------|---------|------| -| @rhwp/core | 0.8.6 | MIT | WASM parser/renderer API | -| @rhwp/editor | 0.8.6 | MIT | iframe 기반 웹 에디터 wrapper | +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -### rhwp-studio +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -| 패키지 | 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 보조 | +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: -### rhwp-chrome / rhwp-firefox + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and -| 패키지 | Lock 버전 | 라이선스 | 용도 | -|--------|-----------|---------|------| -| typescript | 7.0.2 | Apache-2.0 | TypeScript 컴파일 | -| vite | 8.2.1 | MIT | 확장 번들 빌드 | + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and -### rhwp-vscode + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and -| 패키지 | 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 | + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. -### rhwp-shared + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -`rhwp-shared`는 현재 별도 외부 npm 의존성이 없는 shared source package다. +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. ---- +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -## 웹 폰트 및 폰트 리소스 +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -`assets/fonts/`에 포함된 canonical 폰트 목록이다. 저작권 보호가 필요한 한컴/Microsoft 폰트는 Git에 -포함하지 않으며, 세부 파일 목록과 폴백 관계는 `assets/fonts/FONTS.md`를 참조한다. +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -| 폰트/리소스 | 라이선스 | 출처 | 비고 | -|-------------|---------|------|------| -| 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 | +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. ---- +END OF TERMS AND CONDITIONS -## 도구 +APPENDIX: How to apply the Apache License to your work. -| 도구 | 라이선스 | 용도 | -|------|---------|------| -| 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 배포 | + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. ---- +Copyright [yyyy] [name of copyright owner] -## 직접 포팅한 알고리즘 +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -### Volexity hwp-extract + https://www.apache.org/licenses/LICENSE-2.0 -- 출처: [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 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. -Modified BSD License +[LICENSE-MIT] +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: -_Copyright © `2024`, `Volexity, Inc`_ +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. -_All rights reserved._ +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. + +=== aead 0.6.1 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2019-2026 The RustCrypto Project Developers +Copyright (c) 2019 MobileCoin, LLC + +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. + +=== aes 0.9.3 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/block-ciphers +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018-2024 The RustCrypto Project Developers +Copyright (c) 2018 Artyom Pavlov + +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. + +=== argon2 0.6.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/password-hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2021-2026 The RustCrypto Project Developers + +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. + +=== arrayref 0.3.9 (BSD-2-Clause) === +Authors: David Roundy +https://github.com/droundy/arrayref +[LICENSE] +Copyright (c) 2015 David Roundy +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. + +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 THE COPYRIGHT +HOLDER OR CONTRIBUTORS 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. + +=== arrayvec 0.7.8 (MIT OR Apache-2.0) === +Authors: bluss +https://github.com/bluss/arrayvec +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) Ulrik Sverdrup "bluss" 2015-2023 + +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. + +=== base64 0.22.1 (MIT OR Apache-2.0) === +Authors: Marshall Pierce +https://github.com/marshallpierce/rust-base64 +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2015 Alice Maz + +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. + +=== base64 0.23.1 (MIT OR Apache-2.0) === +Authors: Marshall Pierce +https://github.com/marshallpierce/rust-base64 +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2025 Alice Maz, Marshall Pierce + +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. + +=== base64ct 1.8.3 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/formats +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) +Copyright (c) 2021-2025 The RustCrypto Project Developers + +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. + +=== bitflags 1.3.2 (MIT/Apache-2.0) === +Authors: The Rust Project Developers +https://github.com/bitflags/bitflags +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2014 The Rust Project Developers + +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. + +=== bitflags 2.13.1 (MIT OR Apache-2.0) === +Authors: The Rust Project Developers +https://github.com/bitflags/bitflags +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (bitflags 1.3.2 의 LICENSE-MIT 와 같은 글) + +=== blake2 0.11.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2017-2026 The RustCrypto Project Developers +Copyright (c) 2017 Artyom Pavlov +Copyright (c) 2015-2016 The blake2-rfc Developers, Cesar Barros + +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. + +=== blake3 1.8.7 (CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception) === +Authors: Jack O'Connor , Samuel Neves +https://github.com/BLAKE3-team/BLAKE3 +[LICENSE_A2] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Jack O'Connor and Samuel Neves + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE_A2LLVM] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Jack O'Connor and Samuel Neves + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +[LICENSE_CC0] +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. + +=== block-buffer 0.12.1 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018-2025 The RustCrypto Project Developers + +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. + +=== block-padding 0.4.2 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (block-buffer 0.12.1 의 LICENSE-MIT 와 같은 글) + +=== bumpalo 3.20.3 (MIT OR Apache-2.0) === +Authors: Nick Fitzgerald +https://github.com/fitzgen/bumpalo +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2019 Nick Fitzgerald + +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. + +=== bytemuck 1.25.2 (Zlib OR Apache-2.0 OR MIT) === +Authors: Lokathor +https://github.com/Lokathor/bytemuck +[LICENSE-APACHE] +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +MIT License + +Copyright (c) 2019 Daniel "Lokathor" Gee. + +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 (including the next paragraph) 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. + +[LICENSE-ZLIB] +Copyright (c) 2019 Daniel "Lokathor" Gee. + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +=== byteorder 1.5.0 (Unlicense OR MIT) === +Authors: Andrew Gallant +https://github.com/BurntSushi/byteorder +[COPYING] +This project is dual-licensed under the Unlicense and MIT licenses. + +You may use this code under the terms of either license. + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2015 Andrew Gallant + +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. + +[UNLICENSE] +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to + +=== byteorder-lite 0.1.0 (Unlicense OR MIT) === +https://github.com/image-rs/byteorder-lite +[LICENSE-MIT] (byteorder 1.5.0 의 LICENSE-MIT 와 같은 글) + +[UNLICENSE] (byteorder 1.5.0 의 UNLICENSE 와 같은 글) + +=== cbc 0.2.1 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/block-modes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018-2022 RustCrypto Developers +Copyright (c) 2018 Artyom Pavlov + +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. + +=== cfb 0.15.0 (MIT) === +Authors: Matthew D. Steele +https://github.com/mdsteele/rust-cfb +[LICENSE] +MIT License + +Copyright (c) 2017 Matthew D. Steele + +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. + +=== cfg-if 1.0.4 (MIT OR Apache-2.0) === +Authors: Alex Crichton +https://github.com/rust-lang/cfg-if +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2014 Alex Crichton + +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. + +=== chacha20 0.10.1 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/stream-ciphers +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2019-2026 The RustCrypto Project Developers + +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. + +=== chacha20poly1305 0.11.0 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/AEADs +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (chacha20 0.10.1 의 LICENSE-MIT 와 같은 글) + +=== cipher 0.5.2 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2016-2025 RustCrypto Developers + +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. + +=== cmov 0.5.4 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2022-2026 The RustCrypto Project Developers + +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. + +=== codepage 0.1.3 (Apache-2.0 OR MIT) === +Authors: Henri Sivonen +https://github.com/hsivonen/codepage +[COPYRIGHT] +codepage is copyright 2018 Mozilla Foundation. + +Licensed under the Apache License, Version 2.0 + or the MIT +license , +at your option. All files in the project carrying such +notice may not be copied, modified, or distributed except +according to those terms. + +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright Mozilla Foundation + +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. + +=== color_quant 1.1.0 (MIT) === +Authors: nwin +https://github.com/image-rs/color_quant.git +[LICENSE] +The MIT License (MIT) + +Copyright (c) 2016 PistonDevelopers + +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. + +=== const-oid 0.10.2 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/formats +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2020-2026 The RustCrypto Project Developers + +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. + +=== constant_time_eq 0.4.2 (CC0-1.0 OR MIT-0 OR Apache-2.0) === +Authors: Cesar Eduardo Barros +https://github.com/cesarb/constant_time_eq +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +[LICENSE-CC0] (blake3 1.8.7 의 LICENSE_CC0 와 같은 글) + +[LICENSE-MIT0] +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. + +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. + +=== core_maths 0.1.1 (MIT) === +Authors: Robert Bastian , Alex Crichton +https://github.com/srijs/rust-crc32fast +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE-MIT] +MIT License + +Copyright (c) 2018 Sam Rijs, Alex Crichton and contributors + +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. + +=== crypto-common 0.2.2 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2021-2026 RustCrypto Developers + +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. + +=== ctutils 0.4.2 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2025-2026 The RustCrypto Project Developers + +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. + +=== curve25519-dalek 5.0.0 (BSD-3-Clause) === +Authors: Isis Lovecruft , Henry de Valence +https://github.com/dalek-cryptography/curve25519-dalek/tree/main/curve25519-dalek +[LICENSE] +Copyright (c) 2016-2021 isis agora lovecruft. All rights reserved. +Copyright (c) 2016-2021 Henry de Valence. 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 copyright holder 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 THE COPYRIGHT +HOLDER OR CONTRIBUTORS 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. + +=== data-url 0.3.2 (MIT OR Apache-2.0) === +Authors: Simon Sapin +https://github.com/servo/rust-url +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2013-2025 The rust-url developers + +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. + +=== des 0.9.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/block-ciphers +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2017 Gulshan Singh + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2017-2024 The RustCrypto Project Developers +Copyright (c) 2017 Gulshan Singh, Antoni Boucher, Artyom Pavlov + +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. + +=== digest 0.11.3 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2017-2025 RustCrypto Developers +Copyright (c) 2017 Artyom Pavlov + +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. + +=== ed25519 3.0.0 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/signatures +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2018-2022 RustCrypto Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2018-2026 RustCrypto Developers + +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. + +=== ed25519-dalek 3.0.0 (BSD-3-Clause) === +Authors: isis lovecruft , Tony Arcieri , Michael Rosenberg +https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek +[LICENSE] +Copyright (c) 2017-2019 isis agora lovecruft. 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 copyright holder 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 THE COPYRIGHT +HOLDER OR CONTRIBUTORS 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. + +=== embedded-io 0.7.1 (MIT OR Apache-2.0) === +https://github.com/rust-embedded/embedded-hal +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2023 The embedded-io authors + +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. + +=== encoding_rs 0.8.42 ((Apache-2.0 OR MIT) AND BSD-3-Clause) === +Authors: Henri Sivonen +https://github.com/hsivonen/encoding_rs +[COPYRIGHT] +encoding_rs is copyright Mozilla Foundation. + +Licensed under the Apache License, Version 2.0 + or the MIT +license , +at your option. All files in the project carrying such +notice may not be copied, modified, or distributed except +according to those terms. + +This crate includes data derived from the data files supplied +with the WHATWG Encoding Standard, which, when incorporated into +source code, are licensed under the BSD 3-Clause License +. + +Test code within encoding_rs is dedicated to the Public Domain when so +designated (see the individual files for PD/CC0-dedicated sections). + +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (codepage 0.1.3 의 LICENSE-MIT 와 같은 글) + +[LICENSE-WHATWG] +Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). 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. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -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 +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 copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +=== equivalent 1.0.2 (Apache-2.0 OR MIT) === +https://github.com/indexmap-rs/equivalent +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2016--2023 + +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. + +=== fax 0.2.7 (MIT) === +Authors: Sebastian K +https://github.com/pdf-rs/fax +[LICENSE] +Copyright © 2021 The pdf-rs contributers. + +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. + +=== fdeflate 0.3.7 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/fdeflate +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +[LICENSE-MIT] +MIT License + +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. + +=== flate2 1.1.10 (MIT OR Apache-2.0) === +Authors: Alex Crichton , Josh Triplett +https://github.com/rust-lang/flate2-rs +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2014-2026 Alex Crichton + +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. + +=== float-cmp 0.9.0 (MIT) === +Authors: Mike Dilger +https://github.com/mikedilger/float-cmp +[LICENSE] +Copyright (c) 2014-2020 Optimal Computing (NZ) Ltd + +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. + +=== fnv 1.0.7 (Apache-2.0 / MIT) === +Authors: Alex Crichton +https://github.com/servo/rust-fnv +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2017 Contributors + +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. + +=== font-types 0.11.3 (MIT OR Apache-2.0) === +https://github.com/googlefonts/fontations +[LICENSE-APACHE] +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +Copyright 2019 Colin Rothfels + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2019 Colin Rothfels + +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. + +=== fontdb 0.23.0 (MIT) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/fontdb +[LICENSE] +The MIT License (MIT) + +Copyright (c) 2020 Yevhenii Reizner + +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. + +=== getrandom 0.4.3 (MIT OR Apache-2.0) === +Authors: The Rand Project Developers +https://github.com/rust-random/getrandom +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2018-2026 The rust-random Project Developers +Copyright (c) 2014 The Rust Project Developers + +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. + +=== gif 0.13.3 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/image-gif +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2015 nwin + +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. + +=== gif 0.14.2 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/image-gif +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (gif 0.13.3 의 LICENSE-MIT 와 같은 글) + +=== half 2.7.1 (MIT OR Apache-2.0) === +Authors: Kathryn Long +https://github.com/VoidStarKat/half-rs +[LICENSE-APACHE] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +MIT License + +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. + +=== hashbrown 0.17.1 (MIT OR Apache-2.0) === +https://github.com/rust-lang/hashbrown +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2016 Amanieu d'Antras + +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. + +=== hmac 0.13.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/MACs +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2017 Artyom Pavlov + +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. + +=== hybrid-array 0.4.14 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/hybrid-array +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (cmov 0.5.4 의 LICENSE-MIT 와 같은 글) + +=== image 0.25.10 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/image +[LICENSE-APACHE] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (fdeflate 0.3.7 의 LICENSE-MIT 와 같은 글) + +=== image-webp 0.2.4 (MIT OR Apache-2.0) === +https://github.com/image-rs/image-webp +[LICENSE-APACHE] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (fdeflate 0.3.7 의 LICENSE-MIT 와 같은 글) + +=== imagesize 0.13.0 (MIT) === +Authors: Maid Dog +https://github.com/Roughsketch/imagesize +[LICENSE] +MIT License + +Copyright (c) 2017 Maiddog + +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. + +=== indexmap 2.14.0 (Apache-2.0 OR MIT) === +https://github.com/indexmap-rs/indexmap +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2016--2017 + +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. + +=== inout 0.2.2 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2022-2025 The RustCrypto Project Developers +Copyright (c) 2022 Artyom Pavlov + +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. + +=== itoa 1.0.18 (MIT OR Apache-2.0) === +Authors: David Tolnay +https://github.com/dtolnay/itoa +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== keccak 0.2.1 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/sponges +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018-2026 The RustCrypto Project Developers + +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. + +=== kem 0.3.0 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] +Copyright 2021 RustCrypto Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] (crypto-common 0.2.2 의 LICENSE-MIT 와 같은 글) + +=== kurbo 0.11.3 (Apache-2.0 OR MIT) === +Authors: Raph Levien +https://github.com/linebender/kurbo +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018 Raph Levien + +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. + +=== kurbo 0.13.1 (Apache-2.0 OR MIT) === +https://github.com/linebender/kurbo +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (kurbo 0.11.3 의 LICENSE-MIT 와 같은 글) + +=== libc 0.2.189 (MIT OR Apache-2.0) === +https://github.com/rust-lang/libc +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) The Rust Project Developers + +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. + +=== libm 0.2.16 (MIT) === +Authors: Alex Crichton , Amanieu d'Antras , Jorge Aparicio , Trevor Gross +https://github.com/rust-lang/compiler-builtins +[LICENSE.txt] +rust-lang/libm as a whole is available for use under the MIT license: + +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ + +As a contributor, you agree that your code can be used under either the MIT +license or the Apache-2.0 license: + +------------------------------------------------------------------------------ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +------------------------------------------------------------------------------ + +This Rust library contains the following copyrights: + + Copyright (c) 2018 Jorge Aparicio + +Portions of this software are derived from third-party works licensed under +terms compatible with the above MIT license: + +* musl libc https://www.musl-libc.org/. This library contains the following + copyright: + + Copyright © 2005-2020 Rich Felker, et al. + +* The CORE-MATH project https://core-math.gitlabpages.inria.fr/. CORE-MATH + routines are available under the MIT license on a per-file basis. + +The musl libc COPYRIGHT file also includes the following notice relevant to +math portions of the library: + +------------------------------------------------------------------------------ +Much of the math library code (src/math/* and src/complex/*) is +Copyright © 1993,2004 Sun Microsystems or +Copyright © 2003-2011 David Schultz or +Copyright © 2003-2009 Steven G. Kargl or +Copyright © 2003-2009 Bruce D. Evans or +Copyright © 2008 Stephen L. Moshier or +Copyright © 2017-2018 Arm Limited +and labelled as such in comments in the individual source files. All +have been licensed under extremely permissive terms. +------------------------------------------------------------------------------ + +Copyright notices are retained in src/* files where relevant. + +=== log 0.4.34 (MIT OR Apache-2.0) === +Authors: The Rust Project Developers +https://github.com/rust-lang/log +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (bitflags 1.3.2 의 LICENSE-MIT 와 같은 글) + +=== memchr 2.8.3 (Unlicense OR MIT) === +Authors: Andrew Gallant , bluss +https://github.com/BurntSushi/memchr +[COPYING] (byteorder 1.5.0 의 COPYING 와 같은 글) + +[LICENSE-MIT] (byteorder 1.5.0 의 LICENSE-MIT 와 같은 글) + +[UNLICENSE] (byteorder 1.5.0 의 UNLICENSE 와 같은 글) + +=== memmap2 0.9.11 (MIT OR Apache-2.0) === +Authors: Dan Burkert , Yevhenii Reizner , The Contributors +https://github.com/RazrFalcon/memmap2-rs +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [2015] [Dan Burkert] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2020 Yevhenii Reizner +Copyright (c) 2015 Dan Burkert + +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. + +=== miniz_oxide 0.8.9 (MIT OR Zlib OR Apache-2.0) === +Authors: Frommi , oyvindln , Rich Geldreich richgel99@gmail.com +https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide +[LICENSE] +MIT License + +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2017 Frommi +Copyright (c) 2017-2024 oyvindln + + +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. + +[LICENSE-APACHE.md] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT.md] +MIT License + +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2017 Frommi +Copyright (c) 2017-2024 oyvindln + +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. + +[LICENSE-ZLIB.md] +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC +Copyright (c) 2020 Frommi +Copyright (c) 2017-2024 oyvindln + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +=== miniz_oxide 0.9.1 (MIT OR Zlib OR Apache-2.0) === +Authors: Frommi , oyvindln , Rich Geldreich richgel99@gmail.com +https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide +[LICENSE] (miniz_oxide 0.8.9 의 LICENSE 와 같은 글) + +[LICENSE-APACHE.md] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT.md] (miniz_oxide 0.8.9 의 LICENSE-MIT.md 와 같은 글) + +[LICENSE-ZLIB.md] (miniz_oxide 0.8.9 의 LICENSE-ZLIB.md 와 같은 글) + +=== ml-dsa 0.1.1 (Apache-2.0 OR MIT) === +https://github.com/RustCrypto/signatures +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 Trail of Bits + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2024-2026 RustCrypto Developers + +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. + +=== ml-kem 0.3.2 (Apache-2.0 OR MIT) === +https://github.com/RustCrypto/KEMs +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2024-2026 RustCrypto Developers + +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. + +=== module-lattice 0.2.3 (Apache-2.0 OR MIT) === +https://github.com/RustCrypto/KEMs +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (ml-kem 0.3.2 의 LICENSE-MIT 와 같은 글) + +=== moxcms 0.8.1 (BSD-3-Clause OR Apache-2.0) === +Authors: Radzivon Bartoshyk +https://github.com/awxkee/moxcms.git +[LICENSE-APACHE.md] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Radzivon Bartoshyk + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE.md] +Copyright (c) Radzivon Bartoshyk. 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 copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +=== num-traits 0.2.19 (MIT OR Apache-2.0) === +Authors: The Rust Project Developers +https://github.com/rust-num/num-traits +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (bitflags 1.3.2 의 LICENSE-MIT 와 같은 글) + +=== once_cell 1.21.4 (MIT OR Apache-2.0) === +Authors: Aleksey Kladov +https://github.com/matklad/once_cell +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== pbkdf2 0.13.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/password-hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2017 Artyom Pavlov +Copyright (c) 2018-2023 The RustCrypto Project Developers + +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. + +=== pcx 0.2.5 (MIT OR Apache-2.0 OR WTFPL) === +Authors: kryptan +https://github.com/kryptan/pcx +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 The PCX Crate Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2024 The PCX Crate Developers + +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. + +[LICENSE-WTFPL] +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. You just DO WHAT THE FUCK YOU WANT TO. + +=== pdf-writer 0.12.1 (MIT OR Apache-2.0) === +Authors: Laurenz +https://github.com/typst/pdf-writer +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (half 2.7.1 의 LICENSE-MIT 와 같은 글) + +=== pico-args 0.5.0 (MIT) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/pico-args +[LICENSE] +Copyright (c) 2019 Yevhenii Reizner + +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. + +=== png 0.17.16 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/image-png +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2015 nwin + +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. + +=== png 0.18.1 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/image-png +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (png 0.17.16 의 LICENSE-MIT 와 같은 글) + +=== poly1305 0.9.1 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/universal-hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2015-2026 The RustCrypto Project Developers + +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. + +=== polycool 0.4.0 (MIT OR Apache-2.0) === +https://github.com/linebender/kurbo +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (kurbo 0.11.3 의 LICENSE-MIT 와 같은 글) + +=== pxfm 0.1.30 (BSD-3-Clause OR Apache-2.0) === +Authors: Radzivon Bartoshyk +https://github.com/awxkee/pxfm +[LICENSE-APACHE.md] (moxcms 0.8.1 의 LICENSE-APACHE.md 와 같은 글) + +[LICENSE.md] (moxcms 0.8.1 의 LICENSE.md 와 같은 글) + +=== quick-error 2.0.1 (MIT/Apache-2.0) === +Authors: Paul Colomiets , Colin Kiegel +http://github.com/tailhook/quick-error +[LICENSE-APACHE] (crc32fast 1.5.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2015 The quick-error Developers + +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. + +=== quick-xml 0.42.0 (MIT) === +https://github.com/tafia/quick-xml +[LICENSE-MIT.md] +The MIT License (MIT) + +Copyright (c) 2016 Johann Tuffe + +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. + +=== rand_core 0.10.1 (MIT OR Apache-2.0) === +Authors: The Rand Project Developers +https://github.com/rust-random/rand_core +[COPYRIGHT] +Copyrights in the Rand project are retained by their contributors. No +copyright assignment is required to contribute to the Rand project. + +For full authorship information, see the version control history. + +Except as otherwise noted (below and/or in individual files), Rand is +licensed under the Apache License, Version 2.0 or + or the MIT license + or , at your option. + +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +[LICENSE-MIT] +Copyright (c) 2018-2026 The Rand Project Developers + +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. + +=== read-fonts 0.39.2 (MIT OR Apache-2.0) === +https://github.com/googlefonts/fontations +[LICENSE-APACHE] (font-types 0.11.3 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (font-types 0.11.3 의 LICENSE-MIT 와 같은 글) + +=== resvg 0.45.1 (Apache-2.0 OR MIT) === +https://github.com/linebender/resvg +[LICENSE-APACHE] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright 2017 the Resvg Authors + +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. + +=== rgb 0.8.53 (MIT) === +Authors: Kornel Lesiński , James Forster +https://github.com/kornelski/rust-rgb +[LICENSE] +MIT License + +Copyright (c) 2019 Kornel + +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 0.8.7 (MIT) === +https://github.com/edwardkim/rhwp +[LICENSE] +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-contracts 0.1.0 (MIT) === + +(crate 에 원문 파일이 없음 - 아래 "라이선스 원문" 의 MIT) + +=== rhwp-ooxml-chart 0.1.0 (MIT) === + +(crate 에 원문 파일이 없음 - 아래 "라이선스 원문" 의 MIT) + +=== rhwp-password-crypto 0.1.0 (MIT) === + +(crate 에 원문 파일이 없음 - 아래 "라이선스 원문" 의 MIT) + +=== roxmltree 0.20.0 (MIT OR Apache-2.0) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/roxmltree +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2018 Yevhenii Reizner + +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. + +=== roxmltree 0.21.1 (MIT OR Apache-2.0) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/roxmltree +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (roxmltree 0.20.0 의 LICENSE-MIT 와 같은 글) + +=== rustc-hash 2.1.3 (Apache-2.0 OR MIT) === +Authors: The Rust Project Developers +https://github.com/rust-lang/rustc-hash +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== rustybuzz 0.20.1 (MIT) === +Authors: Caleb Maclennan , Laurenz Stampfl , Yevhenii Reizner , خالد حسني (Khaled Hosny) +https://github.com/harfbuzz/rustybuzz +[LICENSE] +The MIT License (MIT) + +Copyright (c) HarfBuzz developers +Copyright (c) 2020 Yevhenii Reizner + +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. + +=== ryu 1.0.23 (Apache-2.0 OR BSL-1.0) === +Authors: David Tolnay +https://github.com/dtolnay/ryu +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-BOOST] +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +=== scopeguard 1.2.0 (MIT OR Apache-2.0) === +Authors: bluss +https://github.com/bluss/scopeguard +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2016-2019 Ulrik Sverdrup "bluss" and scopeguard developers + +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. + +=== serde 1.0.229 (MIT OR Apache-2.0) === +Authors: Erick Tryzelaar , David Tolnay +https://github.com/serde-rs/serde +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== serde_core 1.0.229 (MIT OR Apache-2.0) === +Authors: Erick Tryzelaar , David Tolnay +https://github.com/serde-rs/serde +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== serde_json 1.0.151 (MIT OR Apache-2.0) === +Authors: Erick Tryzelaar , David Tolnay +https://github.com/serde-rs/json +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== sha1 0.11.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2016-2026 The RustCrypto Project Developers +Copyright (c) 2016 Artyom Pavlov +Copyright (c) 2009-2013 Mozilla Foundation +Copyright (c) 2006-2009 Graydon Hoare + +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. + +=== sha2 0.11.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (sha1 0.11.0 의 LICENSE-MIT 와 같은 글) + +=== sha3 0.11.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/hashes +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2020-2026 The RustCrypto Project Developers +Copyright (c) 2016-2023 Artyom Pavlov, Marek Kotewicz +Copyright (c) 2014 Sébastien Martini +Copyright (c) 2009-2013 Mozilla Foundation +Copyright (c) 2006-2009 Graydon Hoare + +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. + +=== shake 0.1.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/XOFs +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2026 The RustCrypto Project Developers + +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. + +=== signature 3.0.0 (Apache-2.0 OR MIT) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (ed25519 3.0.0 의 LICENSE-MIT 와 같은 글) + +=== simd-adler32 0.3.10 (MIT) === +Authors: Marvin Countryman +https://github.com/mcountryman/simd-adler32 +[LICENSE.md] +MIT License + +Copyright (c) [2021] [Marvin Countryman] + +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. + +=== simdutf8 0.1.5 (MIT OR Apache-2.0) === +Authors: Hans Kratz +https://github.com/rusticstuff/simdutf8 +[LICENSE-Apache] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (half 2.7.1 의 LICENSE-MIT 와 같은 글) + +=== simplecss 0.2.2 (Apache-2.0 OR MIT) === +https://github.com/linebender/simplecss +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2018 Reizner Evgeniy + +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. + +=== siphasher 1.0.3 (MIT/Apache-2.0) === +Authors: Frank Denis +https://github.com/jedisct1/rust-siphash +[COPYING] +Copyright 2012-2016 The Rust Project Developers. +Copyright 2016-2026 Frank Denis. + +Licensed under the Apache License, Version 2.0 or the MIT license +, at your +option. + +=== skrifa 0.42.1 (MIT OR Apache-2.0) === +https://github.com/googlefonts/fontations +[LICENSE-APACHE] (font-types 0.11.3 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (font-types 0.11.3 의 LICENSE-MIT 와 같은 글) + +=== slotmap 1.1.1 (Zlib) === +Authors: Orson Peters +https://github.com/orlp/slotmap +[LICENSE] +Copyright (c) 2021 Orson Peters + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated but is + not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + +=== smallvec 1.15.2 (MIT OR Apache-2.0) === +Authors: The Servo Project Developers +https://github.com/servo/rust-smallvec +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018 The Servo Project Developers + +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. + +=== snafu 0.9.2 (MIT OR Apache-2.0) === +Authors: Jake Goulding +https://github.com/shepmaster/snafu +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2019- Jake Goulding + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +Copyright (c) 2019- Jake Goulding + +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. + +=== sponge-cursor 0.1.0 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (shake 0.1.0 의 LICENSE-MIT 와 같은 글) + +=== strict-num 0.1.1 (MIT) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/strict-num +[LICENSE] +Copyright (c) 2022 Yevhenii Reizner + +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. + +=== strum 0.28.0 (MIT) === +Authors: Peter Glotfelty +https://github.com/Peternator7/strum +[LICENSE] +MIT License + +Copyright (c) 2019 Peter Glotfelty + +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. + +=== subsetter 0.2.6 (MIT OR Apache-2.0) === +Authors: Laurenz Mädje , Laurenz Stampfl +https://github.com/typst/subsetter +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (half 2.7.1 의 LICENSE-MIT 와 같은 글) + +[NOTICE] +# Code + +ttf-parser +Code from ttf-parser was used/adapted for the following parts: +- Reader, Writer, LazyArray16 and LazyArray16Iter +- `name` table parsing. +- `hmtx` table parsing. +- Most of the `cff` parsing logic as well auxiliary structs and functions +such as `calc_subroutine_bias`, `SIDMetadata`, `CIDMetadata` `RealNumber`, +`Index`, `IndexSize`, `OffsetSize`, `FDSelect`, `ArgumentStack`, `Top Dict` and `FONT Dict`, +`DictionaryParser`. + +The MIT license applies: + +Copyright (c) 2018 Yevhenii Reizner + +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. + +============================================================================== + +fonttools +Code from fonttools was used/adapted for the following parts: +- The `Decompiler` for charstrings. + +The MIT license applies: +MIT License + +Copyright (c) 2017 Just van Rossum + +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. + +# Fonts + +The SIL OPEN FONT LICENSE Version 1.1 applies to the following fonts: +- fonts/ClickerScript-Regular.ttf +- fonts/MPLUS1p-Regular.ttf +- fonts/Cantarell-VF.otf +- fonts/NotoSans-Regular.ttf +- fonts/NotoSans-Regular_var.ttf +- fonts/NotoSansCJKsc-Bold-subset1.otf +- fonts/NotoSansCJKsc-Regular.otf +- fonts/Syne-Regular_subset.oft (Copyright 2017 The Syne Project Authors (https://gitlab.com/bonjour-monde/fonderie/syne-typeface)) + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +============================================================================== + +The Bitstream Vera Fonts license applies to the following fonts: +- fonts/DejaVuSansMono.ttf + +Bitstream Vera Fonts Copyright +——————————————— + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license (“Fonts”) and associated +documentation files (the “Font Software”), to reproduce and distribute the +Font Software, including without limitation the rights to use, copy, merge, +publish, distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to the +following conditions: + +The above copyright and trademark notices and this permission notice shall +be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional glyphs or characters may be added to the Fonts, only if the fonts +are renamed to names not containing either the words “Bitstream” or the word +“Vera”. + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the “Bitstream +Vera” names. + +The Font Software may be sold as part of a larger software package but no +copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING +ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +——————————————— + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license (“Fonts”) and +associated documentation files (the “Font Software”), to reproduce +and distribute the modifications to the Bitstream Vera Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to +the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words “Tavmjong Bah” or the word “Arev”. + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +“Tavmjong Bah Arev” names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +============================================================================== + +The GUST font license applies to the following fonts: +- fonts/LatinModernRoman-Regular.otf +- fonts/EndcharInSubroutine_1.otf (derived from LatinModernRoman-Regular.otf) +- fonts/NewCMMath-Regular.otf + +% This is version 1.0, dated 22 June 2009, of the GUST Font License. +% (GUST is the Polish TeX Users Group, http://www.gust.org.pl) +% +% For the most recent version of this license see +% http://www.gust.org.pl/fonts/licenses/GUST-FONT-LICENSE.txt +% or +% http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt +% +% This work may be distributed and/or modified under the conditions +% of the LaTeX Project Public License, either version 1.3c of this +% license or (at your option) any later version. +% +% Please also observe the following clause: +% 1) it is requested, but not legally required, that derived works be +% distributed only after changing the names of the fonts comprising this +% work and given in an accompanying "manifest", and that the +% files comprising the Work, as listed in the manifest, also be given +% new names. Any exceptions to this request are also given in the +% manifest. +% +% We recommend the manifest be given in a separate file named +% MANIFEST-.txt, where is some unique identification +% of the font family. If a separate "readme" file accompanies the Work, +% we recommend a name of the form README-.txt. +% +% The latest version of the LaTeX Project Public License is in +% http://www.latex-project.org/lppl.txt and version 1.3c or later +% is part of all distributions of LaTeX version 2006/05/20 or later. + +============================================================================== + +The Apache 2.0 license applies to the following fonts: +- fonts/Roboto-Regular.ttf + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +============================================================================== + +The MIT license applies to the following fonts: +- fonts/TestTTC.ttc + +MIT License + +Copyright (c) 2017 Just van Rossum + +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. + +=== subtle 2.6.1 (BSD-3-Clause) === +Authors: Isis Lovecruft , Henry de Valence +https://github.com/dalek-cryptography/subtle +[LICENSE] +Copyright (c) 2016-2017 Isis Agora Lovecruft, Henry de Valence. All rights reserved. +Copyright (c) 2016-2024 Isis Agora Lovecruft. 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 copyright holder 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 THE COPYRIGHT +HOLDER OR CONTRIBUTORS 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. ---- +=== svg2pdf 0.13.0 (MIT OR Apache-2.0) === +Authors: Laurenz Stampfl , Martin Haug +https://github.com/typst/svg2pdf +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) -## 참조한 오픈소스 프로젝트 (스펙·설계 참조) +[LICENSE-MIT] (half 2.7.1 의 LICENSE-MIT 와 같은 글) -rhwp는 아래 프로젝트들의 **코드를 직접 복사하지 않으며**, 공개된 스펙 정보(enum 값·속성 기본값·태그 이름·검증 규칙 등)만 참조한다. -Apache-2.0 라이선스 고지 의무는 본 문서, 관련 기술 문서, 그리고 각 참조 파일의 헤더 주석으로 충족한다. +[NOTICE] +================================================================================ +The Creative Commons Zero v1.0 Universal License applies to: +* The ICC profiles found in `src/icc/*` -| 프로젝트 | 라이선스 | 참조 범위 | 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` | +================================================================================ ---- +CC0 1.0 Universal -## 라이선스 호환성 +Statement of Purpose -rhwp는 **MIT 라이선스**로 배포된다. +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). -- 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`에서 목록과 폴백만 관리 +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. ---- +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. -*이 문서는 `Cargo.toml`, `Cargo.lock`, 각 `package.json`/`package-lock.json`, `assets/fonts/FONTS.md` 기준으로 현행화되었으며, 의존성 업데이트 시 함께 갱신해야 한다.* +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see +http://creativecommons.org/publicdomain/zero/1.0/ + +================================================================================ +The CC BY-SA 4.0 License applies to: + +* The file '/tests/custom/integration/wikimedia/coat_of_the_arms_of_edinburgh_city_council.svg' + Euryrel, via Wikimedia Commons + +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. +================================================================================ + +================================================================================ +The Mozilla Public License Version 2.0 applies to: + +* The `image_rect`, `fit_view_box` and `calc_node_bbox` functions in `src/util/helper.rs` + and parts of the 'draw_path' method in 'src/render/path.rs' + in the resvg repository (https://github.com/RazrFalcon/resvg) + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. +================================================================================ + +================================================================================ +The Mozilla Public License Version 2.0 applies to: + +* The SVG tests in `tests/svg/resvg` + from the `resvg` repository (https://github.com/RazrFalcon/resvg) +* The general code structure of the test suite in `tests`, which was heavily inspired + `resvg`. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. +================================================================================ + +================================================================================ +The SIL Open Font License Version 1.1 applies to: + +* Noto fonts in tests/fonts/Noto*.ttf + Copyright 2018 The Noto Project Authors + (https://github.com/googlei18n/noto-fonts) +* `MPLUS1P-Regular.ttf` in tests/fonts + Copyright 2016 The M+ Project Authors. +* `Amiri-Regular.ttf` in tests/fonts + Copyright 2010-2022 The Amiri Project Authors (https://github.com/aliftype/amiri). +* `SedgqickAveDisplay-Regular.ttf` in tests/fonts + Copyright 2017 The Sedgwick Ave Project Authors (https://github.com/googlefonts/sedgwickave) +* `SourceSansPro-Regular.ttf` in tests/fonts + Copyright 2010-2018 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. +================================================================================ + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +================================================================================ +The Apache 2.0 License applies to: + +* `Yellowtail-Regular.ttf` in tests/fonts + +Attribution-ShareAlike 4.0 International + +======================================================================= + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ + +=== svgtypes 0.15.3 (Apache-2.0 OR MIT) === +https://github.com/linebender/svgtypes +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2018 Yevhenii Reizner + +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. + +=== svgtypes 0.16.1 (Apache-2.0 OR MIT) === +https://github.com/linebender/svgtypes +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (svgtypes 0.15.3 의 LICENSE-MIT 와 같은 글) + +=== tiff 0.11.3 (MIT) === +Authors: The image-rs Developers +https://github.com/image-rs/image-tiff +[LICENSE] +MIT License + +Copyright (c) 2018 PistonDevelopers + +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. + +=== tiny-skia 0.11.4 (BSD-3-Clause) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/tiny-skia +[LICENSE] +Copyright (c) 2011 Google Inc. All rights reserved. +Copyright (c) 2020 Yevhenii Reizner All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * 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. + + * Neither the name of the copyright holder 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 THE COPYRIGHT +OWNER OR CONTRIBUTORS 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. + +=== tiny-skia-path 0.11.4 (BSD-3-Clause) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/tiny-skia/tree/master/path +[LICENSE] (tiny-skia 0.11.4 의 LICENSE 와 같은 글) + +=== tinyvec 1.12.0 (Zlib OR Apache-2.0 OR MIT) === +Authors: Lokathor +https://github.com/Lokathor/tinyvec +[LICENSE-APACHE.md] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT.md] +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. + +[LICENSE-ZLIB.md] (bytemuck 1.25.2 의 LICENSE-ZLIB 와 같은 글) + +=== tinyvec_macros 0.1.1 (MIT OR Apache-2.0 OR Zlib) === +Authors: Soveu +https://github.com/Soveu/tinyvec_macros +[LICENSE-APACHE.md] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Tomasz "Soveu" Marx + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE-MIT.md] +MIT License + +Copyright (c) 2020 Soveu + +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. + +[LICENSE-ZLIB.md] +zlib License + +(C) 2020 Tomasz "Soveu" Marx + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +=== ttf-parser 0.25.1 (MIT OR Apache-2.0) === +Authors: Caleb Maclennan , Laurenz Stampfl , Yevhenii Reizner , خالد حسني (Khaled Hosny) +https://github.com/harfbuzz/ttf-parser +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (svgtypes 0.15.3 의 LICENSE-MIT 와 같은 글) + +=== typed-path 0.12.3 (MIT OR Apache-2.0) === +Authors: Chip Senkbeil +https://github.com/chipsenkbeil/typed-path +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== typenum 1.20.1 (MIT OR Apache-2.0) === +https://github.com/paholg/typenum +[LICENSE] +MIT OR Apache-2.0 + +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2014 Paho Lurie-Gregg + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) 2014 Paho Lurie-Gregg + +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. + +=== unicode-bidi 0.3.18 (MIT OR Apache-2.0) === +Authors: The Servo Project Developers +https://github.com/servo/unicode-bidi +[COPYRIGHT] +This project is copyright 2015, The Servo Project Developers (given in the +file AUTHORS). + +Licensed under the Apache License, Version 2.0 or the MIT license , at your option. All files in the project +carrying such notice may not be copied, modified, or distributed except +according to those terms. + +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2015 The Rust Project Developers + +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. + +=== unicode-bidi-mirroring 0.4.0 (MIT/Apache-2.0) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/unicode-bidi-mirroring +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (fontdb 0.23.0 의 LICENSE 와 같은 글) + +=== unicode-ccc 0.4.0 (MIT/Apache-2.0) === +Authors: Yevhenii Reizner +https://github.com/RazrFalcon/unicode-ccc +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (fontdb 0.23.0 의 LICENSE 와 같은 글) + +=== unicode-ident 1.0.24 ((MIT OR Apache-2.0) AND Unicode-3.0) === +Authors: David Tolnay +https://github.com/dtolnay/unicode-ident +[LICENSE-APACHE] (constant_time_eq 0.4.2 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +[LICENSE-UNICODE] +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2023 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE 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 OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +=== unicode-properties 0.1.4 (MIT/Apache-2.0) === +Authors: Charles Lew , Manish Goregaokar +https://github.com/unicode-rs/unicode-properties +[COPYRIGHT] +Licensed under the Apache License, Version 2.0 + or the MIT +license , +at your option. All files in the project carrying such +notice may not be copied, modified, or distributed except +according to those terms. + +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (unicode-bidi 0.3.18 의 LICENSE-MIT 와 같은 글) + +=== unicode-script 0.5.8 (MIT OR Apache-2.0) === +Authors: Manish Goregaokar +https://github.com/unicode-rs/unicode-script +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2021 The Unicode-rs Developers + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[LICENSE-MIT] +MIT License + +Copyright (c) 2019 Manish Goregaokar + +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. + +=== unicode-segmentation 1.13.3 (MIT OR Apache-2.0) === +Authors: kwantam , Manish Goregaokar +https://github.com/unicode-rs/unicode-segmentation +[COPYRIGHT] (unicode-properties 0.1.4 의 COPYRIGHT 와 같은 글) + +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (unicode-bidi 0.3.18 의 LICENSE-MIT 와 같은 글) + +=== unicode-vo 0.1.0 (MIT/Apache-2.0) === +Authors: Evgeniy Reizner +https://github.com/RazrFalcon/unicode-vo +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (simplecss 0.2.2 의 LICENSE-MIT 와 같은 글) + +=== unicode-width 0.2.2 (MIT OR Apache-2.0) === +Authors: kwantam , Manish Goregaokar +https://github.com/unicode-rs/unicode-width +[COPYRIGHT] (unicode-properties 0.1.4 의 COPYRIGHT 와 같은 글) + +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (unicode-bidi 0.3.18 의 LICENSE-MIT 와 같은 글) + +=== universal-hash 0.6.1 (MIT OR Apache-2.0) === +Authors: RustCrypto Developers +https://github.com/RustCrypto/traits +[LICENSE-APACHE] (aead 0.6.1 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2019-2025 RustCrypto Developers + +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. + +=== usvg 0.45.1 (Apache-2.0 OR MIT) === +https://github.com/linebender/resvg +[LICENSE-APACHE] (fdeflate 0.3.7 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (resvg 0.45.1 의 LICENSE-MIT 와 같은 글) + +=== uuid 1.24.1 (Apache-2.0 OR MIT) === +Authors: Ashley Mannix, Dylan DPC, Hunar Roop Kahlon +https://github.com/uuid-rs/uuid +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +Copyright (c) 2014 The Rust Project Developers +Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon + +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. + +=== wasm-bindgen 0.2.127 (MIT OR Apache-2.0) === +Authors: The wasm-bindgen Developers +https://github.com/wasm-bindgen/wasm-bindgen +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (cfg-if 1.0.4 의 LICENSE-MIT 와 같은 글) + +=== wasm-bindgen-shared 0.2.127 (MIT OR Apache-2.0) === +Authors: The wasm-bindgen Developers +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/shared +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (cfg-if 1.0.4 의 LICENSE-MIT 와 같은 글) + +=== web-time 1.1.0 (MIT OR Apache-2.0) === +https://github.com/daxpedda/web-time +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 dAxpeDDa + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE-MIT] +MIT License + +Copyright (c) 2023 dAxpeDDa + +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. + +=== weezl 0.1.12 (MIT OR Apache-2.0) === +Authors: The image-rs Developers +https://github.com/image-rs/weezl +[LICENSE-APACHE] (arrayvec 0.7.8 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +The MIT License (MIT) + +Copyright (c) HeroicKatora 2020 + +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. + +=== write-fonts 0.48.1 (MIT OR Apache-2.0) === +https://github.com/googlefonts/fontations +[LICENSE-APACHE] (font-types 0.11.3 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (font-types 0.11.3 의 LICENSE-MIT 와 같은 글) + +=== xmlwriter 0.1.0 (MIT) === +Authors: Evgeniy Reizner +https://github.com/RazrFalcon/xmlwriter +[LICENSE] +The MIT License (MIT) + +Copyright (c) 2019 Reizner Evgeniy + +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. + +=== zerocopy 0.8.56 (BSD-2-Clause OR Apache-2.0 OR MIT) === +https://github.com/google/zerocopy +[LICENSE-APACHE] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 The Fuchsia Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +[LICENSE-BSD] +Copyright 2019 The Fuchsia Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * 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. + +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 THE COPYRIGHT +OWNER OR CONTRIBUTORS 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. + +[LICENSE-MIT] +Copyright 2023 The Fuchsia Authors + +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. + +=== zeroize 1.9.0 (Apache-2.0 OR MIT) === +Authors: The RustCrypto Project Developers +https://github.com/RustCrypto/utils +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (keccak 0.2.1 의 LICENSE-MIT 와 같은 글) + +=== zip 8.6.0 (MIT) === +Authors: Mathijs van de Nes , Marli Frost , Ryan Levick , Chris Hennick +https://github.com/zip-rs/zip2 +[LICENSE] +The MIT License (MIT) + +Copyright (c) 2014 Mathijs van de Nes + +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. + +=== zlib-rs 0.6.7 (Zlib) === +https://github.com/trifectatechfoundation/zlib-rs +[LICENSE] +(C) 2024 Trifecta Tech Foundation + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. + +=== zmij 1.0.23 (MIT) === +Authors: David Tolnay +https://github.com/dtolnay/zmij +[LICENSE-MIT] (adler2 2.0.1 의 LICENSE-MIT 와 같은 글) + +=== zopfli 0.8.3 (Apache-2.0) === +https://github.com/zopfli-rs/zopfli +[COPYING] +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2011 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +=== zune-core 0.4.12 (MIT OR Apache-2.0 OR Zlib) === + +(crate 에 원문 파일이 없음 - 아래 "라이선스 원문" 의 MIT OR Apache-2.0 OR Zlib) + +=== zune-core 0.5.3 (MIT OR Apache-2.0 OR Zlib) === +https://github.com/etemesi254/zune-image +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] +MIT License + +Copyright (c) zune-image developers + +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. + +[LICENSE-ZLIB] +zlib License + +(C) zune-image developers + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +=== zune-jpeg 0.4.21 (MIT OR Apache-2.0 OR Zlib) === +Authors: caleb +https://github.com/etemesi254/zune-image/tree/dev/crates/zune-jpeg +(crate 에 원문 파일이 없음 - 아래 "라이선스 원문" 의 MIT OR Apache-2.0 OR Zlib) + +=== zune-jpeg 0.5.15 (MIT OR Apache-2.0 OR Zlib) === +Authors: caleb +https://github.com/etemesi254/zune-image/tree/dev/crates/zune-jpeg +[LICENSE-APACHE] (cmov 0.5.4 의 LICENSE-APACHE 와 같은 글) + +[LICENSE-MIT] (zune-core 0.5.3 의 LICENSE-MIT 와 같은 글) + +[LICENSE-ZLIB] (zune-core 0.5.3 의 LICENSE-ZLIB 와 같은 글) + +--------------------------------------------------------------------- +라이선스 원문 (원문 파일이 없는 crate 용) +--------------------------------------------------------------------- + +=== MIT (원문: adler2 2.0.1 의 파일) === +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. + +=== Apache-2.0 (원문: adler2 2.0.1 의 파일) === +Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/LICENSE-2.0 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=== Zlib (원문: bytemuck 1.25.2 의 파일) === +Copyright (c) 2019 Daniel "Lokathor" Gee. + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. diff --git a/integration_test/hwp_source_test.dart b/integration_test/hwp_source_test.dart index d292a66..2356b4b 100644 --- a/integration_test/hwp_source_test.dart +++ b/integration_test/hwp_source_test.dart @@ -22,7 +22,7 @@ class _FakeEngine implements HwpPdfEngine { @override Future open(String path, {String? password}) async { - if (password == 'bad') throw const HwpPdfException('암호가 틀림'); + if (password == 'bad') throw const HwpPdfException(HwpErrorKind.passwordWrong, 'CryptoError WrongPassword'); return _FakeDoc(this); } } @@ -40,11 +40,11 @@ class _FakeDoc implements HwpPdfDoc { // 끝나지 않는 변환 (시간 제한 시험): 풀려도 아무것도 쓰지 않는다 (시험이 끝난 뒤 폴더에 쓰지 않게) if (e.hold != null) { await e.hold!.future; - throw const HwpPdfException('늦게 끝남'); + throw const HwpPdfException(HwpErrorKind.cancelled, 'late'); } if (e.failNext > 0) { e.failNext--; - throw const HwpPdfException('렌더 실패'); + throw const HwpPdfException(HwpErrorKind.render, 'page 1: boom'); } final docs = []; for (var i = from; i < to; i++) { @@ -62,6 +62,9 @@ class _FakeDoc implements HwpPdfDoc { } } + @override + void cancel() {} + @override Future close() async => closed = true; } @@ -106,7 +109,7 @@ void main() { await tester.runAsync(() => src!.load(0, maxWidth: 200).then((r) => r.dispose(), onError: (Object e) { err = e; })); - expect('$err', contains('렌더 실패')); + expect('$err', contains('이 쪽을 그리지 못했습니다')); final ok = await tester.runAsync(() => src!.load(0, maxWidth: 200)); expect(ok!.image, isNotNull, reason: '실패한 묶음은 다시'); ok.dispose(); @@ -129,6 +132,6 @@ void main() { await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, password: 'bad').then((_) {}, onError: (Object e) { err3 = e; })); - expect('$err3', contains('암호')); + expect('$err3', contains('암호가 맞지 않습니다')); }); } diff --git a/lib/core/hwp_source.dart b/lib/core/hwp_source.dart index 680dd57..35fb729 100644 --- a/lib/core/hwp_source.dart +++ b/lib/core/hwp_source.dart @@ -43,12 +43,45 @@ abstract class HwpPdfDoc { /// [from] 부터 [to] 앞까지 (0 부터) 를 PDF 파일 [out] 으로. [fontDirs] 는 글꼴을 찾을 폴더들 Future exportPdf(int from, int to, String out, {List fontDirs = const []}); + + /// 지금 바꾸는 것을 멈춘다 (시간 제한을 넘겼을 때 · 닫을 때 - 관리자 지시 10/20: 기다림만 끊으면 뒤에서 계속 돌아 + /// 다음 묶음이 줄을 서서 연달아 실패했다). 멈춘 바꾸기는 [HwpErrorKind.cancelled] 로 끝난다 + void cancel(); Future close(); } +/// 관리자 지시 (10/20, 28 지적): 변환기 오류의 종류 - 화면에는 종류에 맞는 번역 글, 원문은 [HwpPdfException.detail] ([자세히] · 작업 기록). +/// 앞 9개는 tool/rhwp_ffi 의 JJ_RHWP_E_* 와 같은 순서 (1 부터) +enum HwpErrorKind { cancelled, read, invalid, passwordRequired, passwordWrong, range, render, write, internal, timeout, closed } + +/// tool/rhwp_ffi 의 오류 코드 (JJ_RHWP_E_*) → 종류 (모르는 코드는 내부 오류) +HwpErrorKind hwpErrorKindOf(int code) => code >= 1 && code <= 9 ? HwpErrorKind.values[code - 1] : HwpErrorKind.internal; + class HwpPdfException implements Exception { - final String message; - const HwpPdfException(this.message); + final HwpErrorKind kind; + + /// 변환기 원문 (화면에는 보이지 않고 [자세히] · 작업 기록에) + final String detail; + + /// 글에 넣을 값 (시간 제한: 첫 쪽 · 끝 쪽 · 초) + final List args; + const HwpPdfException(this.kind, [this.detail = '', this.args = const []]); + + /// 화면에 보일 글 (화면 언어) + String get message => switch (kind) { + HwpErrorKind.cancelled => tr('그만두었습니다'), + HwpErrorKind.read => tr('파일을 읽을 수 없습니다'), + HwpErrorKind.invalid => tr('HWP · HWPX 문서로 읽을 수 없습니다 (깨졌거나 지원하지 않는 형식)'), + HwpErrorKind.passwordRequired => tr('암호가 걸린 문서입니다'), + HwpErrorKind.passwordWrong => tr('암호가 맞지 않습니다'), + HwpErrorKind.range => tr('쪽 범위가 맞지 않습니다'), + HwpErrorKind.render => tr('이 쪽을 그리지 못했습니다'), + HwpErrorKind.write => tr('바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)'), + HwpErrorKind.internal => tr('변환기 안에서 오류가 났습니다'), + HwpErrorKind.timeout => trf('{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다', args), + HwpErrorKind.closed => tr('닫힌 문서입니다'), + }; + @override String toString() => message; } @@ -121,7 +154,10 @@ class HwpSource extends ReaderSource { var _disposed = false; HwpSource._(this.path, this.doc, this.work, this.title, - {required this.fontDirs, required this.chunk, required this.timeout, required this.maxOpenChunks, required this.opener}); + {required this.fontDirs, required this.chunk, required this.timeout, required this.maxOpenChunks, required this.opener, this.log}); + + /// 실패의 원문을 남길 곳 (작업 기록 - 파일 탐색기가 AppController.note 를 준다) + final void Function(String line)? log; /// 문서를 연다 (WebDAV 등은 [tempDir] 로 받은 뒤). 열지 못하면 [HwpPdfException] static Future open(String path, @@ -132,12 +168,19 @@ class HwpSource extends ReaderSource { int chunk = 5, Duration timeout = const Duration(minutes: 3), int maxOpenChunks = 3, - HwpChunkOpener opener = pdfChunkOpener}) async { + HwpChunkOpener opener = pdfChunkOpener, + void Function(String line)? log}) async { final local = await vLocalCopy(path, tempDir); - final doc = await engine.open(local, password: password); + final HwpPdfDoc doc; + try { + doc = await engine.open(local, password: password); + } on HwpPdfException catch (e) { + log?.call('HWP ${vBasename(path)}: ${e.kind.name} ${e.detail}'); + rethrow; + } 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, maxOpenChunks: math.max(2, maxOpenChunks), opener: opener); + fontDirs: fontDirs, chunk: chunk, timeout: timeout, maxOpenChunks: math.max(2, maxOpenChunks), opener: opener, log: log); } @override @@ -176,10 +219,14 @@ class HwpSource extends ReaderSource { try { await doc.exportPdf(from, to, out, fontDirs: fontDirs).timeout(timeout); } on TimeoutException { - throw HwpPdfException(trf('{0}~{1}쪽을 {2}초 안에 바꾸지 못했습니다', [from + 1, to, timeout.inSeconds])); - } catch (_) { - // 닫은 뒤에 끝난 변환은 (임시 폴더를 이미 지워) 실패한다 - 그 이유 대신 "닫힌 문서" + // 관리자 지시 (10/20): 기다림만 끊지 않고 변환도 멈춘다 (다음 묶음이 줄 서서 실패하지 않게) + doc.cancel(); + log?.call('HWP ${vBasename(path)}: timeout ${from + 1}~$to'); + throw HwpPdfException(HwpErrorKind.timeout, 'timeout ${timeout.inSeconds}s', [from + 1, to, timeout.inSeconds]); + } catch (e) { + // 닫은 뒤에 끝난 변환은 (임시 폴더를 이미 지워 · 멈춰) 실패한다 - 그 이유 대신 "닫힌 문서" if (_disposed) throw StateError(tr('닫힌 문서입니다')); + if (e is HwpPdfException) log?.call('HWP ${vBasename(path)} ${from + 1}~$to: ${e.kind.name} ${e.detail}'); rethrow; } _made.add(k); @@ -232,7 +279,8 @@ class HwpSource extends ReaderSource { for (final f in open) { unawaited(f.then((c) => c.dispose(), onError: (Object _) {})); } - await doc.close(); // 바꾸는 중이면 그것이 끝난 뒤에 닫힌다 (엔진이 한 번에 하나씩) + doc.cancel(); // 바꾸는 중이면 다음 쪽에서 멈춘다 + 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 index 6864502..e0b679b 100644 --- a/lib/platform/android/hwp_pdf_ffi.dart +++ b/lib/platform/android/hwp_pdf_ffi.dart @@ -8,6 +8,7 @@ import '../../core/hwp_source.dart'; /// 관리자 지시 (10/20): Android 의 HWP · HWPX → PDF - APK 에 든 libjj_rhwp.so (rhwp v0.8.7, MIT - tool/build_rhwp_android.sh · /// tool/rhwp_ffi) 를 dart:ffi 로 부른다. 여는 것 · 바꾸는 것은 다른 isolate 에서 (화면을 막지 않게), 한 문서는 한 번에 하나씩. +/// 라이브러리는 HWP 를 처음 열 때 읽는다 (앱을 켤 때 읽지 않음). class RhwpEngine implements HwpPdfEngine { static const libName = 'libjj_rhwp.so'; @@ -27,8 +28,8 @@ class RhwpEngine implements HwpPdfEngine { @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 (handle, code, err) = await Isolate.run(() => _open(path, password ?? '')); + if (handle == 0) throw HwpPdfException(hwpErrorKindOf(code), err ?? ''); final pages = await Isolate.run(() => _pageCount(handle)); return _RhwpDoc(handle, pages); } @@ -40,7 +41,7 @@ class _RhwpDoc implements HwpPdfDoc { final int pageCount; var _closed = false; - /// 한 번에 하나씩 (rhwp 문서는 여러 스레드에서 함께 쓰지 않는다) + /// 한 번에 하나씩 (rhwp 문서는 여러 스레드에서 함께 바꾸지 않는다) Future _queue = Future.value(); _RhwpDoc(this._handle, this.pageCount); @@ -53,12 +54,18 @@ class _RhwpDoc implements HwpPdfDoc { @override Future exportPdf(int from, int to, String out, {List fontDirs = const []}) => _serial(() async { - if (_closed) throw const HwpPdfException('closed'); + if (_closed) throw const HwpPdfException(HwpErrorKind.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); + final (code, err) = await Isolate.run(() => _export(h, from, to, dirs, out)); + if (code != 0) throw HwpPdfException(hwpErrorKindOf(code), err ?? ''); }); + /// 지금 바꾸는 것을 멈춘다 (줄을 서지 않고 바로 - 다른 isolate 의 바꾸기가 다음 쪽에서 끝난다) + @override + void cancel() { + if (!_closed) _cancel(_handle); + } + @override Future close() => _serial(() async { if (_closed) return; @@ -69,13 +76,13 @@ class _RhwpDoc implements HwpPdfDoc { // ───────── C 함수 (isolate 안에서 부른다 - 핸들은 주소 숫자로 넘긴다) ───────── -typedef _OpenC = Pointer Function(Pointer, Pointer, Pointer>); +typedef _OpenC = Pointer Function(Pointer, 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 _ExportC = Int32 Function(Pointer, Int32, Int32, Pointer, Pointer, Pointer, Pointer>); +typedef _ExportD = int Function(Pointer, int, int, Pointer, Pointer, Pointer, Pointer>); +typedef _VoidC = Void Function(Pointer); +typedef _VoidD = void Function(Pointer); typedef _FreeC = Void Function(Pointer); typedef _FreeD = void Function(Pointer); @@ -90,37 +97,43 @@ String? _takeError(DynamicLibrary lib, Pointer> err) { return s; } -(int, String?) _open(String path, String password) { +(int, int, String?) _open(String path, String password) { final lib = _lib(); final open = lib.lookupFunction<_OpenC, _OpenC>('jj_rhwp_open'); final a = path.toNativeUtf8(), b = password.toNativeUtf8(); + final code = calloc(); final err = calloc>(); try { - final doc = open(a, b, err); - return (doc.address, _takeError(lib, err)); + final doc = open(a, b, code, err); + return (doc.address, code.value, _takeError(lib, err)); } finally { calloc.free(a); calloc.free(b); + calloc.free(code); calloc.free(err); } } int _pageCount(int handle) => _lib().lookupFunction<_CountC, _CountD>('jj_rhwp_page_count')(Pointer.fromAddress(handle)); -String? _export(int handle, int from, int to, String fontDirs, String out) { +(int, String?) _export(int handle, int from, int to, String fontDirs, String out) { final lib = _lib(); final export = lib.lookupFunction<_ExportC, _ExportD>('jj_rhwp_export_pdf'); final d = fontDirs.toNativeUtf8(), o = out.toNativeUtf8(); + final code = calloc(); final err = calloc>(); try { - final r = export(Pointer.fromAddress(handle), from, to, d, o, err); + final r = export(Pointer.fromAddress(handle), from, to, d, o, code, err); final e = _takeError(lib, err); - return r == 0 ? null : (e ?? 'rhwp'); + return r == 0 ? (0, null) : (code.value == 0 ? 9 : code.value, e); } finally { calloc.free(d); calloc.free(o); + calloc.free(code); calloc.free(err); } } -void _close(int handle) => _lib().lookupFunction<_CloseC, _CloseD>('jj_rhwp_close')(Pointer.fromAddress(handle)); +void _cancel(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_cancel')(Pointer.fromAddress(handle)); + +void _close(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_close')(Pointer.fromAddress(handle)); diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index b6d59f9..712b1b4 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -1805,7 +1805,7 @@ class _ExplorerPageState extends State { HwpSource? src; Object? error; try { - src = await HwpSource.open(path, engine: RhwpEngine(), tempDir: temp, fontDirs: RhwpEngine.fontDirs); + src = await HwpSource.open(path, engine: RhwpEngine(), tempDir: temp, fontDirs: RhwpEngine.fontDirs, log: c.note); } catch (e) { error = e; } diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index f21251d..e9601e3 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -7,7 +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/hwp_source.dart' show HwpPdfException, HwpSource; import '../core/vfs.dart'; import '../l10n/tr.dart'; import 'ai_image_page.dart'; @@ -448,6 +448,18 @@ class _ReaderPageState extends State { child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Text(trf('열 수 없습니다: {0}', [e]), textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70)), const SizedBox(height: 8), + // 관리자 지시 (10/20, 28 지적): 변환기 원문은 화면 글 대신 [자세히] 에 + if (e is HwpPdfException && e.detail.isNotEmpty) + TextButton( + onPressed: () => showDialog( + context: context, + builder: (ctx) => AlertDialog( + content: SelectableText(e.detail, style: const TextStyle(fontSize: 12)), + actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(tr('닫기')))], + ), + ), + child: Text(tr('자세히')), + ), OutlinedButton( onPressed: () => setState(() { _cache.remove(slot.page); diff --git a/test/hwp_source_unit_test.dart b/test/hwp_source_unit_test.dart index ccfbe83..862f276 100644 --- a/test/hwp_source_unit_test.dart +++ b/test/hwp_source_unit_test.dart @@ -12,6 +12,7 @@ class _Engine implements HwpPdfEngine { final calls = <(int, int)>[]; Completer? hold; var closed = false; + var cancels = 0; _Engine(this.pages); @override @@ -32,6 +33,9 @@ class _Doc implements HwpPdfDoc { File(out).writeAsStringSync('$from-$to'); } + @override + void cancel() => e.cancels++; + @override Future close() async => e.closed = true; } @@ -145,7 +149,9 @@ void main() { err = x; } expect(err, isA()); + expect((err! as HwpPdfException).kind, HwpErrorKind.timeout); expect('$err', allOf(contains('1~5'), contains('0초'))); + expect(e.cancels, 1, reason: '시간 제한을 넘기면 변환도 멈춘다 (기다림만 끊지 않음)'); expect(s.openChunks, isNot(contains(0)), reason: '실패한 묶음은 기억하지 않음 - [다시 시도] 가 다시 만든다'); e.hold!.complete(); e.hold = null; @@ -161,6 +167,7 @@ void main() { final pending = s.load(0, maxWidth: 100).then((_) => null, onError: (Object x) => x); await settle(); await s.dispose(); // 기다리지 않고 바로 끝난다 + expect(e.cancels, 1, reason: '닫으면 바꾸던 것을 멈춘다'); e.hold!.complete(); final r = await pending; expect(r, isA(), reason: '닫힌 문서'); @@ -181,4 +188,22 @@ void main() { expect(other.existsSync(), isTrue, reason: '이름이 jj_hwp_ 로 시작하지 않음 (jj_hwpx_…)'); expect(file.existsSync(), isTrue, reason: '폴더만'); }); + + test('변환기 오류 코드 → 종류 · 화면 글 (tool/rhwp_ffi 의 JJ_RHWP_E_* 순서), 원문은 detail 에만', () { + expect([for (var c = 1; c <= 9; c++) hwpErrorKindOf(c)], [ + HwpErrorKind.cancelled, HwpErrorKind.read, HwpErrorKind.invalid, HwpErrorKind.passwordRequired, HwpErrorKind.passwordWrong, + HwpErrorKind.range, HwpErrorKind.render, HwpErrorKind.write, HwpErrorKind.internal, // + ]); + expect(hwpErrorKindOf(0), HwpErrorKind.internal); + expect(hwpErrorKindOf(42), HwpErrorKind.internal); + const e = HwpPdfException(HwpErrorKind.passwordRequired, '비밀번호가 필요한 암호 문서입니다 (parse_document_with_password …)'); + expect('$e', '암호가 걸린 문서입니다', reason: '화면에는 원문 대신 번역 글'); + expect(e.detail, contains('parse_document_with_password')); + // 원본 lib.rs 의 코드 값과 같은지 (순서가 바뀌면 화면 글이 틀린다) + final rs = File('tool/rhwp_ffi/src/lib.rs').readAsStringSync(); + const names = ['CANCELLED', 'READ', 'INVALID', 'PASSWORD_REQUIRED', 'PASSWORD_WRONG', 'RANGE', 'RENDER', 'WRITE', 'INTERNAL']; + for (final (i, n) in names.indexed) { + expect(rs, contains('pub const JJ_RHWP_E_$n: c_int = ${i + 1};'), reason: n); + } + }); } diff --git a/test/licenses_test.dart b/test/licenses_test.dart index 45ba664..8b79ab2 100644 --- a/test/licenses_test.dart +++ b/test/licenses_test.dart @@ -165,4 +165,30 @@ void main() { expect(a, contains('[패키지 라이선스]')); expect(a, contains('GPLv3')); }); + + // 관리자 지시 (10/20, 28 지적): Android HWP 보기 (libjj_rhwp.so) - 실제로 링크되는 Rust crate 마다 라이선스 · 원문 + // (tool/licenses/rhwp_crates.py 를 tool/build_rhwp_android.sh 가 부름). Cargo.lock 이 바뀌었는데 다시 만들지 않으면 여기서 잡힌다 + test('rhwp: Cargo.lock 의 모든 crate 가 "linked" 나 "not linked" 에 있고, 링크되는 crate 마다 원문 절', () { + final t = File('assets/licenses/RHWP_LICENSES.txt').readAsStringSync().replaceAll('\r\n', '\n'); + final lock = File('tool/rhwp_ffi/Cargo.lock').readAsStringSync().replaceAll('\r\n', '\n'); + final locked = { + for (final m in RegExp(r'\[\[package\]\]\s*\nname = "([^"]+)"\s*\nversion = "([^"]+)"').allMatches(lock)) '${m[1]} ${m[2]}' + }; + final linked = {for (final m in RegExp(r'^linked: (\S+) (\S+) ', multiLine: true).allMatches(t)) '${m[1]} ${m[2]}'}; + final notLinked = {for (final m in RegExp(r'^not linked: (\S+) (\S+)$', multiLine: true).allMatches(t)) '${m[1]} ${m[2]}'}; + expect(linked, isNotEmpty); + expect(linked.intersection(notLinked), isEmpty); + expect({...linked, ...notLinked, 'jj_rhwp 0.1.0'}, locked, reason: 'Cargo.lock 이 바뀌면 sh tool/build_rhwp_android.sh 로 다시 만든다'); + for (final c in linked) { + expect(t, contains('=== $c ('), reason: '$c 의 라이선스 원문 절'); + } + // 28 이 짚은 것들 (Cargo.lock 에 있으면 어느 한쪽 목록에 - 링크되는지는 cargo tree 가 정함) + for (final n in ['tiny-skia', 'zlib-rs', 'curve25519-dalek', 'unicode-ident', 'rustybuzz', 'skrifa', 'miniz_oxide', 'fontdb']) { + for (final c in locked.where((c) => c.startsWith('$n '))) { + expect(linked.contains(c) || notLinked.contains(c), isTrue, reason: c); + } + } + expect(t, contains('rhwp v0.8.7')); + expect(t, contains('1a76570e833917d15817415a53c09ad61ab3203f')); + }); } diff --git a/tool/build_rhwp_android.sh b/tool/build_rhwp_android.sh index b92ab5b..75c756d 100644 --- a/tool/build_rhwp_android.sh +++ b/tool/build_rhwp_android.sh @@ -4,7 +4,9 @@ # 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 에 함께 둔다. +# 라이선스: rhwp MIT (LICENSE → third_party/rhwp) · .so 에 실제로 링크되는 crate 마다 이름 · 판 · 라이선스 · 원문은 +# tool/licenses/rhwp_crates.py 가 assets/licenses/RHWP_LICENSES.txt 로 만든다 (관리자 지시 10/20 - 저장소의 THIRD_PARTY_LICENSES.md 는 +# npm 쪽이 섞여 있고 링크되는 crate 가 빠져 쓰지 않는다). Cargo.lock 이 바뀌면 test/licenses_test.dart 가 다시 만들라고 잡는다. # 도구: 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 로 바꿔 적는다. @@ -84,7 +86,9 @@ 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" +rm -f "$OUT/../THIRD_PARTY_LICENSES.md" +# 링크되는 crate 의 라이선스 · 원문 (cargo tree · metadata - 의존은 위 빌드가 받아 둠) +python "$APP/tool/licenses/rhwp_crates.py" "$WORK/ffi/Cargo.toml" "$SRC" "$VER" "$REV" "$APP/assets/licenses/RHWP_LICENSES.txt" if [ -n "$JJ_RHWP_CHECK" ]; then rm -rf "$WORK/target2" build "$WORK/target2" "$WORK/again.so" diff --git a/tool/licenses/rhwp_crates.py b/tool/licenses/rhwp_crates.py new file mode 100644 index 0000000..96f5d8d --- /dev/null +++ b/tool/licenses/rhwp_crates.py @@ -0,0 +1,140 @@ +"""관리자 지시 (10/20, 28 지적): libjj_rhwp.so 에 실제로 링크되는 Rust crate 마다 이름 · 판 · 라이선스와 원문 · 저작권 고지를 모아 +assets/licenses/RHWP_LICENSES.txt 를 만든다 (tool/build_rhwp_android.sh 가 빌드 뒤에 부른다). + +- 링크되는 것: cargo tree --target aarch64-linux-android -e normal,no-proc-macro (빌드 전용 · 개발 · proc-macro 는 .so 에 들어가지 않음) +- 원문: crate 폴더 맨 위의 LICENSE* · LICENCE* · COPYING* · NOTICE* · COPYRIGHT* · UNLICENSE* (cargo metadata 의 manifest_path) + 원문 파일이 없는 crate 는 같은 라이선스의 원문을 맨 뒤 "라이선스 원문" 에 한 번씩 넣는다. +- Cargo.lock 의 나머지는 "링크되지 않음" 목록에 적는다 - test/licenses_test.dart 가 두 목록의 합이 Cargo.lock 과 같은지 본다 + (Cargo.lock 이 바뀌었는데 이 파일을 다시 만들지 않으면 시험이 잡는다). +- rhwp 저장소의 THIRD_PARTY_LICENSES.md (npm 쪽 포함) 는 쓰지 않는다. + +사용: python tool/licenses/rhwp_crates.py <출력 파일> + (cargo 는 PATH · CARGO_HOME 의 것, 의존은 이미 받아 둔 것 - --offline --locked) +""" +import hashlib +import json +import os +import re +import subprocess +import sys + +manifest, rhwp_src, version, commit, out = sys.argv[1:6] +TARGET = 'aarch64-linux-android' + + +def cargo(*args): + r = subprocess.run(['cargo', *args, '--manifest-path', manifest, '--locked', '--offline'], + capture_output=True, text=True, encoding='utf-8') + if r.returncode != 0: + sys.exit('cargo %s 실패:\n%s' % (args[0], r.stderr)) + return r.stdout + + +# 1. 링크되는 crate (이름, 판) +linked = set() +for line in cargo('tree', '--target', TARGET, '-e', 'normal,no-proc-macro', '--prefix', 'none', '--format', '{p}').splitlines(): + m = re.match(r'^(\S+) v(\S+)', line.strip()) + if m: + linked.add((m.group(1), m.group(2))) + +# 2. 모든 crate 의 폴더 · 라이선스 +meta = json.loads(cargo('metadata', '--format-version', '1', '--filter-platform', TARGET)) +pkgs = {(p['name'], p['version']): p for p in meta['packages']} + +# 3. Cargo.lock 의 모든 crate +lock = open(os.path.join(os.path.dirname(manifest), 'Cargo.lock'), encoding='utf-8').read() +locked = set(re.findall(r'\[\[package\]\]\s*\nname = "([^"]+)"\s*\nversion = "([^"]+)"', lock)) +missing = linked - locked +if missing: + sys.exit('Cargo.lock 에 없는 crate: %s' % sorted(missing)) + +ours = {'jj_rhwp'} +NAMES = re.compile(r'^(LICEN[CS]E|COPYING|NOTICE|COPYRIGHT|UNLICENSE)', re.I) + + +def license_files(pkg): + d = os.path.dirname(pkg['manifest_path']) + files = [] + for n in sorted(os.listdir(d)): + p = os.path.join(d, n) + if os.path.isfile(p) and NAMES.match(n): + files.append((n, open(p, encoding='utf-8', errors='replace').read().strip())) + if pkg.get('license_file'): + p = os.path.join(d, pkg['license_file']) + if os.path.isfile(p) and os.path.basename(p) not in [f for f, _ in files]: + files.append((os.path.basename(p), open(p, encoding='utf-8', errors='replace').read().strip())) + return files + + +# 원문이 없는 crate 를 위해 라이선스 이름 → 원문 하나 (같은 이름의 원문 파일 하나만 가진 crate 에서) +SPDX_FILE = {'MIT': r'LICEN[CS]E-?MIT', 'Apache-2.0': r'LICEN[CS]E-?APACHE', 'BSD-3-Clause': r'LICEN[CS]E-?BSD', + 'Zlib': r'LICEN[CS]E-?ZLIB', 'BSL-1.0': r'LICEN[CS]E-?BOOST', 'Unicode-3.0': r'LICEN[CS]E-?UNICODE'} +spdx_text = {} +sections = [] +seen_text = {} +no_file = [] +for key in sorted(linked, key=lambda k: (k[0].lower(), k[1])): + name, ver = key + pkg = pkgs[key] + lic = pkg.get('license') or ('see ' + pkg['license_file'] if pkg.get('license_file') else '?') + if name in ours: + continue + files = license_files(pkg) + for fname, text in files: + for spdx, pat in SPDX_FILE.items(): + if re.fullmatch(pat + r'(\.(txt|md))?', fname, re.I) and spdx not in spdx_text: + spdx_text[spdx] = (text, '%s %s' % (name, ver)) + body = [] + for fname, text in files: + h = hashlib.sha256(text.encode()).hexdigest() + if h in seen_text: + body.append('[%s] (%s 의 %s 와 같은 글)' % (fname, *seen_text[h])) + else: + seen_text[h] = ('%s %s' % (name, ver), fname) + body.append('[%s]\n%s' % (fname, text)) + if not files: + no_file.append((name, ver, lic)) + body.append('(crate 에 원문 파일이 없음 - 아래 "라이선스 원문" 의 %s)' % lic) + authors = ', '.join(pkg.get('authors') or []) + sections.append('=== %s %s (%s) ===\n%s%s\n%s' % ( + name, ver, lic, 'Authors: %s\n' % authors if authors else '', pkg.get('repository') or '', '\n\n'.join(body))) + +not_linked = sorted(locked - linked, key=lambda k: (k[0].lower(), k[1])) +lines = [ + 'rhwp v%s (https://github.com/edwardkim/rhwp, commit %s) - MIT' % (version, commit), + 'Android: libjj_rhwp.so (HWP / HWPX -> PDF), built by tool/build_rhwp_android.sh with tool/rhwp_ffi (MIT)', + 'This file lists every Rust crate linked into libjj_rhwp.so (target %s, normal dependencies,' % TARGET, + 'build-only / dev / proc-macro crates excluded) with its license and the license / notice files it ships.', + '', + '---------------------------------------------------------------------', + 'rhwp LICENSE', + '---------------------------------------------------------------------', + open(os.path.join(rhwp_src, 'LICENSE'), encoding='utf-8').read().strip(), + '', + '---------------------------------------------------------------------', + 'Linked crates (%d)' % len(linked - {(n, v) for n, v in linked if n in ours}), + '---------------------------------------------------------------------', +] +for name, ver in sorted(linked, key=lambda k: (k[0].lower(), k[1])): + if name not in ours: + lines.append('linked: %s %s (%s)' % (name, ver, pkgs[(name, ver)].get('license') or '?')) +lines += ['', '---------------------------------------------------------------------', + 'Not linked into libjj_rhwp.so (build tools, proc-macros, other platforms) - listed for completeness', + '---------------------------------------------------------------------'] +lines += ['not linked: %s %s' % k for k in not_linked] +lines += ['', '---------------------------------------------------------------------', 'License and notice files', + '---------------------------------------------------------------------', ''] +lines += [s + '\n' for s in sections] +need = sorted({l for _, _, l in no_file}) +if need: + lines += ['---------------------------------------------------------------------', '라이선스 원문 (원문 파일이 없는 crate 용)', + '---------------------------------------------------------------------', ''] + for lic in need: + for spdx in re.findall(r'[A-Za-z0-9.\-]+', lic): + if spdx in spdx_text: + text, src = spdx_text.pop(spdx) + lines += ['=== %s (원문: %s 의 파일) ===' % (spdx, src), text, ''] +text = '\n'.join(lines).rstrip() + '\n' +open(out, 'wb').write(text.replace('\n', '\r\n').encode('utf-8')) +print('linked %d · not linked %d · 원문 파일 없음 %d %s' % (len(linked) - 1, len(not_linked), len(no_file), + [f'{n} {v}' for n, v, _ in no_file])) diff --git a/tool/rhwp_ffi/src/lib.rs b/tool/rhwp_ffi/src/lib.rs index f801f96..c2975a3 100644 --- a/tool/rhwp_ffi/src/lib.rs +++ b/tool/rhwp_ffi/src/lib.rs @@ -1,21 +1,49 @@ //! 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). +//! (큰 문서는 앞쪽부터 몇 쪽씩 보이면서 뒤를 이어 만든다). Dart 는 dart:ffi 로 부른다 (lib/platform/android/hwp_pdf_ffi.dart). +//! - 오류는 종류 코드 (JJ_RHWP_E_*) 로 돌려주고, 원문 (rhwp 의 글) 은 err 로 (화면은 코드로 고른 번역 글, 원문은 [자세히] · 작업 기록). +//! - jj_rhwp_cancel: 다른 스레드에서 지금 바꾸는 것을 멈춘다 (쪽마다 확인 - 관리자 지시 10/20, 시간 제한 뒤에도 계속 돌지 않게). //! 오류 글 · 돌려준 글은 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 std::sync::atomic::{AtomicBool, Ordering}; use rhwp::document_core::DocumentCore; -use rhwp::renderer::pdf::PdfExportOptions; +use rhwp::paint::RenderProfile; +use rhwp::renderer::pdf::{svgs_to_pdf_with_options, PdfExportOptions}; + +/// 성공 +pub const JJ_RHWP_OK: c_int = 0; +/// jj_rhwp_cancel 로 멈춤 +pub const JJ_RHWP_E_CANCELLED: c_int = 1; +/// 파일을 읽지 못함 +pub const JJ_RHWP_E_READ: c_int = 2; +/// HWP · HWPX 로 읽지 못함 (깨짐 · 지원하지 않는 형식) +pub const JJ_RHWP_E_INVALID: c_int = 3; +/// 비밀번호가 필요한 문서 +pub const JJ_RHWP_E_PASSWORD_REQUIRED: c_int = 4; +/// 비밀번호가 틀림 +pub const JJ_RHWP_E_PASSWORD_WRONG: c_int = 5; +/// 쪽 범위가 맞지 않음 (앱 잘못) +pub const JJ_RHWP_E_RANGE: c_int = 6; +/// 쪽을 그리지 못함 +pub const JJ_RHWP_E_RENDER: c_int = 7; +/// PDF 를 저장하지 못함 +pub const JJ_RHWP_E_WRITE: c_int = 8; +/// 변환기 안의 예기치 않은 오류 (panic) +pub const JJ_RHWP_E_INTERNAL: c_int = 9; /// 연 문서 pub struct JjRhwpDoc { core: DocumentCore, + cancel: AtomicBool, } +struct Fail(c_int, String); + fn text(p: *const c_char) -> Option { if p.is_null() { return None; @@ -23,19 +51,26 @@ fn text(p: *const c_char) -> Option { 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; +fn set_out(code: *mut c_int, err: *mut *mut c_char, c: c_int, msg: &str) { + if !code.is_null() { + unsafe { *code = c }; + } + if !err.is_null() { + let s = CString::new(msg.replace('\0', " ")).unwrap_or_default(); + unsafe { *err = s.into_raw() }; } - 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 { +fn guarded(code: *mut c_int, 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); + Ok(Ok(v)) => { + if !code.is_null() { + unsafe { *code = JJ_RHWP_OK }; + } + v + } + Ok(Err(Fail(c, msg))) => { + set_out(code, err, c, &msg); fail } Err(p) => { @@ -44,30 +79,54 @@ fn guarded(err: *mut *mut c_char, fail: T, f: impl FnOnce() -> Result().cloned()) .unwrap_or_else(|| "panic".to_string()); - set_err(err, &format!("rhwp panic: {msg}")); + set_out(code, err, JJ_RHWP_E_INTERNAL, &format!("rhwp panic: {msg}")); fail } } } +/// 여는 오류의 종류: rhwp v0.8.7 의 오류 글 (ParseError 의 Display) 로 암호 문서를 가린다 +fn open_kind(msg: &str, with_password: bool) -> c_int { + if msg.contains("비밀번호가 필요한 암호 문서") { + JJ_RHWP_E_PASSWORD_REQUIRED + } else if msg.contains("암호 오류") { + if with_password { + JJ_RHWP_E_PASSWORD_WRONG + } else { + JJ_RHWP_E_PASSWORD_REQUIRED + } + } else { + JJ_RHWP_E_INVALID + } +} + /// 판 (예: "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 +/// 문서 열기 (password 는 null 이나 "" 이면 없음). 실패하면 null 과 code · 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()) { +pub extern "C" fn jj_rhwp_open( + path: *const c_char, + password: *const c_char, + code: *mut c_int, + err: *mut *mut c_char, +) -> *mut JjRhwpDoc { + guarded(code, err, null_mut(), || { + let path = text(path).ok_or_else(|| Fail(JJ_RHWP_E_READ, "no path".into()))?; + let data = std::fs::read(&path).map_err(|e| Fail(JJ_RHWP_E_READ, e.to_string()))?; + let pw = text(password).filter(|p| !p.is_empty()); + let core = match &pw { 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 }))) + .map_err(|e| { + let msg = e.to_string(); + Fail(open_kind(&msg, pw.is_some()), msg) + })?; + Ok(Box::into_raw(Box::new(JjRhwpDoc { core, cancel: AtomicBool::new(false) }))) }) } @@ -81,8 +140,19 @@ pub extern "C" fn jj_rhwp_page_count(doc: *const JjRhwpDoc) -> c_int { catch_unwind(AssertUnwindSafe(|| doc.core.page_count() as c_int)).unwrap_or(-1) } +/// 지금 바꾸는 것을 멈춘다 (다른 스레드에서 불러도 됨 - 다음 쪽에서 JJ_RHWP_E_CANCELLED 로 끝난다). +/// 다음 jj_rhwp_export_pdf 는 표시를 지우고 시작한다 +#[no_mangle] +pub extern "C" fn jj_rhwp_cancel(doc: *const JjRhwpDoc) { + if !doc.is_null() { + unsafe { &*doc }.cancel.store(true, Ordering::SeqCst); + } +} + /// [from, to) 쪽 (0 부터) 을 PDF 파일 out 으로. font_dirs 는 줄바꿈 (\n) 으로 나눈 글꼴 폴더들 (Android 는 시스템 글꼴을 -/// 스스로 찾지 않으므로 /system/fonts 와 앱이 받은 한글 글꼴 폴더를 넘긴다). 성공 0, 실패 -1 과 err +/// 스스로 찾지 않으므로 /system/fonts 와 앱이 받은 한글 글꼴 폴더를 넘긴다). 성공 0, 실패 -1 과 code · err. +/// rhwp 의 render_pages_pdf_native_with_options 와 같게 (Print 로 쪽마다 SVG → 한 PDF) 하되, 쪽마다 멈춤 표시를 본다 +/// (하이퍼링크는 넣지 않는다 - 보기용) #[no_mangle] pub extern "C" fn jj_rhwp_export_pdf( doc: *const JjRhwpDoc, @@ -90,18 +160,20 @@ pub extern "C" fn jj_rhwp_export_pdf( to: c_int, font_dirs: *const c_char, out: *const c_char, + code: *mut c_int, err: *mut *mut c_char, ) -> c_int { - guarded(err, -1, || { + guarded(code, err, -1, || { if doc.is_null() { - return Err("문서 없음".into()); + return Err(Fail(JJ_RHWP_E_RANGE, "no document".into())); } let doc = unsafe { &*doc }; + doc.cancel.store(false, Ordering::SeqCst); let count = doc.core.page_count() as c_int; if from < 0 || to > count || from >= to { - return Err(format!("쪽 범위가 맞지 않음: {from}..{to} / {count}")); + return Err(Fail(JJ_RHWP_E_RANGE, format!("pages {from}..{to} / {count}"))); } - let out = text(out).ok_or("저장할 곳 없음")?; + let out = text(out).ok_or_else(|| Fail(JJ_RHWP_E_WRITE, "no output path".into()))?; let mut options = PdfExportOptions::default(); options.font_paths = text(font_dirs) .unwrap_or_default() @@ -110,12 +182,29 @@ pub extern "C" fn jj_rhwp_export_pdf( .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 cancelled = || doc.cancel.load(Ordering::SeqCst); + let mut svgs = Vec::with_capacity((to - from) as usize); + for page in from as u32..to as u32 { + if cancelled() { + return Err(Fail(JJ_RHWP_E_CANCELLED, "cancelled".into())); + } + svgs.push( + doc.core + .render_page_svg_layer_with_profile_native(page, RenderProfile::Print) + .map_err(|e| Fail(JJ_RHWP_E_RENDER, format!("page {}: {e}", page + 1)))?, + ); + } + if cancelled() { + return Err(Fail(JJ_RHWP_E_CANCELLED, "cancelled".into())); + } + let bytes = svgs_to_pdf_with_options(&svgs, &options).map_err(|e| Fail(JJ_RHWP_E_RENDER, e))?; + if cancelled() { + return Err(Fail(JJ_RHWP_E_CANCELLED, "cancelled".into())); + } // 다 쓴 뒤에 이름을 바꾼다 (보기 화면이 반쪽 파일을 읽지 않게) 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())?; + std::fs::write(&part, &bytes).map_err(|e| Fail(JJ_RHWP_E_WRITE, e.to_string()))?; + std::fs::rename(&part, &out).map_err(|e| Fail(JJ_RHWP_E_WRITE, e.to_string()))?; Ok(0) }) } From 0d2b14ce68ae89fb4d7f09dca7c081532c79f680 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:04:45 +0900 Subject: [PATCH 07/24] =?UTF-8?q?WIP=20HWP:=20=EB=8B=AB=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=EB=8B=A4=20=EC=97=B0=20PDF=20=EB=AC=B6=EC=9D=8C?= =?UTF-8?q?=EC=9D=80=20=EB=8B=AB=EA=B8=B0=EB=A5=BC=20=EA=B8=B0=EB=8B=A4?= =?UTF-8?q?=EB=A6=BC=20(Windows=20=EB=8A=94=20=EC=97=B0=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=9D=84=20=EC=9E=A1=EC=95=84=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=A5=BC=20=EB=AA=BB=20=EC=A7=80=EC=9B=A0?= =?UTF-8?q?=EC=9D=8C=20-=20=ED=86=B5=ED=95=A9=20=EC=8B=9C=ED=97=98?= =?UTF-8?q?=EC=9D=B4=20=EC=9E=A1=EC=9D=8C),=20=EB=A7=8C=EB=93=9C=EB=8A=94?= =?UTF-8?q?=20=EC=A4=91=EC=9D=B8=20=EA=B2=83=EB=A7=8C=20=EA=B8=B0=EB=8B=A4?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EC=95=8A=EC=9D=8C=20=C2=B7=20hwp=5Fpdf=5F?= =?UTF-8?q?ffi=20=EC=A4=84=20=EB=81=9D=20CRLF=20(=EA=B2=80=ED=86=A0=20?= =?UTF-8?q?=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 --- lib/core/hwp_source.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/hwp_source.dart b/lib/core/hwp_source.dart index 35fb729..2a595e5 100644 --- a/lib/core/hwp_source.dart +++ b/lib/core/hwp_source.dart @@ -275,9 +275,11 @@ class HwpSource extends ReaderSource { _disposed = true; final open = [..._open.values]; _open.clear(); - // 만드는 중인 묶음은 기다리지 않는다 (몇 분 걸릴 수 있음) - 끝나면 닫힌다 ([_openChunk] 가 _disposed 를 본다) + // 다 연 묶음은 닫기를 기다리고 (열린 PDF 를 닫아야 임시 폴더를 지울 수 있다 - Windows 는 연 파일을 잡는다), + // 만드는 중인 묶음은 기다리지 않는다 (몇 분 걸릴 수 있음 - 끝나면 스스로 닫힌다, [_openChunk] 가 _disposed 를 본다) for (final f in open) { - unawaited(f.then((c) => c.dispose(), onError: (Object _) {})); + final closing = f.then((c) => c.dispose(), onError: (Object _) {}); + await Future.any([closing, Future.delayed(const Duration(milliseconds: 200))]); } doc.cancel(); // 바꾸는 중이면 다음 쪽에서 멈춘다 await doc.close(); // 멈춘 바꾸기가 끝난 뒤에 닫힌다 (엔진이 한 번에 하나씩) From 76fd2f3cbfed95fa775026beee0679ac8281623e Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:19:05 +0900 Subject: [PATCH 08/24] =?UTF-8?q?WIP=20HWP=20ABI=20=ED=8C=90=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20,=2028=20=EC=A7=80=EC=A0=81=20-=20=EC=98=9B=20.so=20?= =?UTF-8?q?=EC=97=90=20=EC=83=88=20=EC=9D=B8=EC=88=98=EB=A5=BC=20=EB=84=98?= =?UTF-8?q?=EA=B8=B0=EB=A9=B4=20=ED=9E=99=20=EC=86=90=EC=83=81):=20Rust=20?= =?UTF-8?q?jj=5Frhwp=5Fabi=5Fversion()=20=3D=202=20=C2=B7=20Dart=20?= =?UTF-8?q?=EB=8A=94=20=EC=B2=98=EC=9D=8C=20HWP=20=EB=A5=BC=20=EC=97=B4=20?= =?UTF-8?q?=EB=95=8C=20=EA=BC=AD=20=EC=9E=88=EC=96=B4=EC=95=BC=20=ED=95=A0?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=207=EA=B0=9C=EC=99=80=20=ED=8C=90?= =?UTF-8?q?=EC=9D=84=20=EB=B3=B4=EA=B3=A0=20=EB=8B=A4=EB=A5=B4=EB=A9=B4=20?= =?UTF-8?q?=EC=95=B1=20=EC=95=88=20=EB=B3=B4=EA=B8=B0=EB=A5=BC=20=EC=93=B0?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EA=B3=A0=20=EB=8B=A4=EB=A5=B8=20=EC=95=B1?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=C2=B7=20check=5Fapk=20=EA=B0=80=20APK=20?= =?UTF-8?q?=EC=95=88=20libjj=5Frhwp.so=20=EC=9D=98=20=EB=82=B4=EB=B3=B4?= =?UTF-8?q?=EB=82=B8=20=EA=B8=B0=ED=98=B8=EB=A5=BC=20NDK=20llvm-nm=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=B4=EA=B3=A0=20=EC=97=86=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EC=8B=A4=ED=8C=A8=20(986=20=EC=9D=98=20=EC=98=9B?= =?UTF-8?q?=20.so=20=EB=A5=BC=20=EC=9E=A1=EC=9D=8C)=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20hwp=5Fabi=5Ftest=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=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 --- lib/platform/android/hwp_pdf_ffi.dart | 30 +++++++++++++++++++++++---- test/hwp_abi_test.dart | 28 +++++++++++++++++++++++++ tool/check_apk.ps1 | 28 +++++++++++++++++++++++-- tool/rhwp_ffi/src/lib.rs | 10 +++++++++ 4 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 test/hwp_abi_test.dart diff --git a/lib/platform/android/hwp_pdf_ffi.dart b/lib/platform/android/hwp_pdf_ffi.dart index e0b679b..40e04ae 100644 --- a/lib/platform/android/hwp_pdf_ffi.dart +++ b/lib/platform/android/hwp_pdf_ffi.dart @@ -16,11 +16,33 @@ class RhwpEngine implements HwpPdfEngine { /// 관리자 판단 (10/20): 한글 글꼴 컴포넌트를 받으면 그 폴더를 앞에 더한다 (아직 없음) static const fontDirs = ['/system/fonts']; - /// 라이브러리를 읽을 수 있는지 (없으면 다른 앱으로 연다) - static bool get available { + /// 이 Dart 가 기대하는 C ABI 의 판 (tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION 과 같아야 함) + static const abiVersion = 2; + + /// 꼭 있어야 하는 C 함수 + static const requiredSymbols = [ + 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_close', 'jj_rhwp_free', // + ]; + + static bool? _available; + + /// 라이브러리를 읽을 수 있고 ABI 가 맞는지 (아니면 다른 앱으로 연다). 처음 HWP 를 열 때 한 번 본다. + /// 관리자 지시 (10/20, 28 지적): 옛 .so (판 1 - code 칸 · cancel 없음) 에 새 인수를 넘기면 힙이 망가지므로, 기호와 판이 맞을 때만 + static bool get available => _available ??= () { + try { + final lib = DynamicLibrary.open(libName); + return abiCompatible( + has: lib.providesSymbol, abi: () => lib.lookupFunction('jj_rhwp_abi_version')()); + } catch (_) { + return false; + } + }(); + + /// [requiredSymbols] 가 모두 있고 [abi] 가 [abiVersion] 인지 (시험용으로 따로 - 기호를 찾다 · 부르다 실패하면 false) + static bool abiCompatible({required bool Function(String name) has, required int Function() abi}) { try { - DynamicLibrary.open(libName); - return true; + if (!requiredSymbols.every(has)) return false; + return abi() == abiVersion; } catch (_) { return false; } diff --git a/test/hwp_abi_test.dart b/test/hwp_abi_test.dart new file mode 100644 index 0000000..4336139 --- /dev/null +++ b/test/hwp_abi_test.dart @@ -0,0 +1,28 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:jj_mkvmaker/platform/android/hwp_pdf_ffi.dart'; + +/// 관리자 지시 (10/20, 28 지적): 옛 libjj_rhwp.so (ABI 판 1 - jj_rhwp_cancel · jj_rhwp_abi_version 없음, 오류 code 칸 없음) 이면 +/// 앱 안 보기를 쓰지 않고 다른 앱으로 연다 (새 인수를 넘기면 힙이 망가진다) +void main() { + const v1 = {'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_close', 'jj_rhwp_free', 'jj_rhwp_version'}; + final v2 = {...v1, 'jj_rhwp_cancel', 'jj_rhwp_abi_version'}; + + test('옛 .so (기호 없음) · 판이 다름 · 부르다 실패 → 쓰지 않음, 맞으면 씀', () { + expect(RhwpEngine.abiCompatible(has: v1.contains, abi: () => throw ArgumentError('no symbol')), isFalse, reason: '판 1'); + expect(RhwpEngine.abiCompatible(has: {...v1, 'jj_rhwp_abi_version'}.contains, abi: () => 2), isFalse, reason: 'cancel 없음'); + expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 1), isFalse, reason: '판이 다름'); + expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 3), isFalse, reason: '더 새 판도'); + expect(RhwpEngine.abiCompatible(has: (_) => throw StateError('x'), abi: () => 2), isFalse); + expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => RhwpEngine.abiVersion), isTrue); + }); + + test('Dart 의 판 = tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION · 꼭 있어야 하는 함수는 lib.rs 에 모두', () { + final rs = File('tool/rhwp_ffi/src/lib.rs').readAsStringSync(); + expect(rs, contains('pub const JJ_RHWP_ABI_VERSION: c_int = ${RhwpEngine.abiVersion};')); + for (final s in RhwpEngine.requiredSymbols) { + expect(rs, contains('pub extern "C" fn $s('), reason: s); + } + }); +} diff --git a/tool/check_apk.ps1 b/tool/check_apk.ps1 index feb5825..8a0763f 100644 --- a/tool/check_apk.ps1 +++ b/tool/check_apk.ps1 @@ -8,6 +8,8 @@ # 새 버전 확인도 "최신과 같음" 이 된다. 시험판은 flutter build apk --build-number 9xx 로 빌드한다. 정식 판 APK 는 -Release. # - 순번 900 이상의 v 태그 (시험판 Pre-release, 예: v2026.10.18_977) 는 정식 판으로 보지 않는다 (관리자 지시 10/19, 28 지적: # 시험판에 태그를 단 뒤 같은 APK 를 다시 검사하면 "정식 판 번호와 같음" 으로 거짓 실패). 정식 판 (-Release) 인데 순번이 900 이상이면 실패. +# - HWP 보기 (관리자 지시 10/20, 28 지적): APK 안 libjj_rhwp.so 가 앱의 Dart 와 같은 C ABI 인지 - 내보낸 기호 (NDK llvm-nm) 에 +# jj_rhwp_cancel · jj_rhwp_abi_version 등이 모두 있어야 한다 (옛 .so 는 이름 · 목록만으로는 못 가린다). # 사용: powershell -ExecutionPolicy Bypass -File tool\check_apk.ps1 -Apk build\app\outputs\flutter-apk\app-release.apk [-Release] param( [Parameter(Mandatory = $true)][string]$Apk, @@ -41,7 +43,7 @@ if (-not $Libs) { $Libs = Join-Path $app 'android\app\third_party_libs.txt' } $listed = @(Get-Content $Libs | ForEach-Object { $_.Trim() } | Where-Object { $_ -and -not $_.StartsWith('#') }) # third_party 에 있는 파일 (목록 밖이면 APK 에 없어야 함) $tp = @() -foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android', 'third_party\7zip\android') { +foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android', 'third_party\7zip\android', 'third_party\rhwp\android') { $dir = Join-Path $app $d if (Test-Path $dir) { $tp += @(Get-ChildItem $dir -Recurse -Filter *.so | ForEach-Object { $_.Name }) } } @@ -50,6 +52,13 @@ try { $libsIn = @($zip.Entries | Where-Object { $_.FullName -like 'lib/arm64-v8a/*.so' } | ForEach-Object { $_.Name }) $dexes = @($zip.Entries | Where-Object { $_.FullName -match '^classes\d*\.dex$' }) if ($dexes.Count -eq 0) { throw "dex 가 없습니다: $Apk" } + # HWP 보기 라이브러리는 꺼내 두고 아래에서 기호를 본다 + $rhwpSo = $null + $rhwpEntry = $zip.Entries | Where-Object { $_.FullName -eq 'lib/arm64-v8a/libjj_rhwp.so' } | Select-Object -First 1 + if ($rhwpEntry) { + $rhwpSo = Join-Path ([System.IO.Path]::GetTempPath()) ("jj_check_rhwp_{0}.so" -f [guid]::NewGuid().ToString('N')) + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($rhwpEntry, $rhwpSo) + } $found = $null foreach ($e in $dexes) { $s = $e.Open() @@ -61,6 +70,21 @@ try { $zip.Dispose() } $bad = @() +# HWP 보기: libjj_rhwp.so 의 내보낸 기호 (lib/platform/android/hwp_pdf_ffi.dart 의 requiredSymbols 와 같게) +$rhwpNote = '' +if ($rhwpSo) { + try { + $nm = Get-ChildItem (Join-Path $app '..\tools\android-sdk\ndk') -Recurse -Filter llvm-nm.exe -ErrorAction SilentlyContinue | Select-Object -Last 1 + if (-not $nm) { throw 'llvm-nm.exe 를 찾지 못했습니다 (tools\android-sdk\ndk)' } + $syms = @(& $nm.FullName -D --defined-only $rhwpSo 2>$null | ForEach-Object { ($_ -split '\s+')[-1] }) + $need = 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_close', 'jj_rhwp_free' + $miss = @($need | Where-Object { $syms -notcontains $_ }) + if ($miss) { $bad += "libjj_rhwp.so 가 옛 C ABI 입니다 - 없는 함수: $($miss -join ', ') (sh tool/build_rhwp_android.sh 로 다시 빌드)" } + else { $rhwpNote = " · libjj_rhwp.so 함수 $($need.Count)개 있음" } + } finally { + Remove-Item $rhwpSo -Force -ErrorAction SilentlyContinue + } +} # 시험판 번호: aapt2 로 versionCode · versionName 을 읽어 정식 판 태그의 번호와 견준다 (build.gradle.kts 와 같은 셈: 년%100 · 월 · 일 · 번호) $aapt = Get-ChildItem (Join-Path $app '..\tools\android-sdk\build-tools') -Recurse -Filter aapt2.exe -ErrorAction SilentlyContinue | Select-Object -Last 1 if (-not $aapt -and $env:ANDROID_HOME) { $aapt = Get-ChildItem (Join-Path $env:ANDROID_HOME 'build-tools') -Recurse -Filter aapt2.exe -ErrorAction SilentlyContinue | Select-Object -Last 1 } @@ -90,4 +114,4 @@ if (-not $found) { Write-Host "실패: GeneratedPluginRegistrant 가 dex 에 없습니다 (켜자마자 꺼지는 APK). flutter pub get 뒤 --no-pub 없이 다시 빌드하세요." -ForegroundColor Red exit 1 } -Write-Host "통과: GeneratedPluginRegistrant 있음 ($found) · third_party 실행 파일 $($listed.Count)개 목록과 같음 · versionCode $code ($vname)$(if ($Release) { ' (정식 판)' } else { ' (시험판 - 정식 판 번호와 다름)' })" +Write-Host "통과: GeneratedPluginRegistrant 있음 ($found) · third_party 실행 파일 $($listed.Count)개 목록과 같음$rhwpNote · versionCode $code ($vname)$(if ($Release) { ' (정식 판)' } else { ' (시험판 - 정식 판 번호와 다름)' })" diff --git a/tool/rhwp_ffi/src/lib.rs b/tool/rhwp_ffi/src/lib.rs index c2975a3..d61fb71 100644 --- a/tool/rhwp_ffi/src/lib.rs +++ b/tool/rhwp_ffi/src/lib.rs @@ -100,6 +100,16 @@ fn open_kind(msg: &str, with_password: bool) -> c_int { } } +/// 이 C ABI 의 판 - Dart (lib/platform/android/hwp_pdf_ffi.dart 의 RhwpEngine.abiVersion) 와 같아야 앱 안 보기를 쓴다 +/// (관리자 지시 10/20, 28 지적: 옛 .so (판 1 - code 칸 · cancel 없음) 에 새 인수를 넘기면 힙이 망가진다). +/// 함수의 인수 · 오류 코드를 바꾸면 올린다 +pub const JJ_RHWP_ABI_VERSION: c_int = 2; + +#[no_mangle] +pub extern "C" fn jj_rhwp_abi_version() -> c_int { + JJ_RHWP_ABI_VERSION +} + /// 판 (예: "0.8.7") - 라이선스 화면 · 작업 기록용. jj_rhwp_free 로 푼다 #[no_mangle] pub extern "C" fn jj_rhwp_version() -> *mut c_char { From 7bc452e54c985992101c707a8de19cee66f73144 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 20:11:11 +0900 Subject: [PATCH 09/24] =?UTF-8?q?WIP=20HWP=20=EA=B3=A0=EC=B9=A8=20(?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20,=2028?= =?UTF-8?q?=20=EA=B2=80=ED=86=A0):=20=E2=91=A0=20=EC=95=B1=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=EC=9D=98=20HWP=20=EC=9E=91=EC=97=85=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=A0=95=EB=A6=AC=EB=A5=BC=20=EA=B8=B0=EB=8B=A4?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EC=95=8A=EC=9D=8C=20(queue=5Ftest=20?= =?UTF-8?q?=EA=B0=80=20=EA=B0=80=EC=A7=9C=20=EC=8B=9C=EA=B0=84=20=EC=95=88?= =?UTF-8?q?=EC=97=90=EC=84=9C=2010=EB=B6=84=20=EA=B1=B8=EB=A6=AC=EB=8D=98?= =?UTF-8?q?=20=ED=9A=8C=EA=B7=80)=20=E2=91=A1=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=EC=9D=80=20=EB=B0=94=EA=BE=B8=EA=B8=B0?= =?UTF-8?q?=EA=B0=80=20=EC=8B=A4=EC=A0=9C=EB=A1=9C=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=EB=90=9C=20=EB=95=8C=EB=B6=80=ED=84=B0=20=C2=B7=20=EB=A9=88?= =?UTF-8?q?=EC=B6=A4=EC=9D=80=20=EC=9A=94=EC=B2=AD=EB=A7=88=EB=8B=A4=20(Qu?= =?UTF-8?q?euedHwpDoc=20=EB=A1=9C=20=EC=A4=84=20=EC=84=B8=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0=20=C2=B7=20=EC=8B=9C=EA=B0=84=20=C2=B7=20=EB=A9=88?= =?UTF-8?q?=EC=B6=A4=EC=9D=84=20FFI=20=EC=97=90=EC=84=9C=20=EB=96=BC?= =?UTF-8?q?=EC=96=B4=20=EC=8B=9C=ED=97=98=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EA=B2=8C),=20Rust=20ABI=203:=20export=20=EC=97=90=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=B2=88=ED=98=B8=20=C2=B7=20jj=5Frhwp=5Fcancel(?= =?UTF-8?q?=EB=B2=88=ED=98=B8)=20=C2=B7=20jj=5Frhwp=5Fcancel=5Fall=20(?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=ED=95=A0=20=EB=95=8C=20=EA=B9=83=EB=B0=9C?= =?UTF-8?q?=EC=9D=84=20=EC=A7=80=EC=9A=B0=EC=A7=80=20=EC=95=8A=EC=9D=8C),?= =?UTF-8?q?=20=EB=8B=AB=EC=9C=BC=EB=A9=B4=20=EC=A4=84=EC=97=90=20=EC=84=A0?= =?UTF-8?q?=20=EA=B2=83=EC=9D=80=20=EC=8B=9C=EC=9E=91=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=8C=20=C2=B7=20check=5Fapk=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=208=EA=B0=9C=20=E2=91=A2=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EC=B0=BD=20close=20=EB=8A=94=20=EA=B7=B8=20?= =?UTF-8?q?=EC=B0=BD=EC=9D=B4=20=EB=A7=A8=20=EC=9C=84=EC=9D=BC=20=EB=95=8C?= =?UTF-8?q?=EB=A7=8C=20=C2=B7=20=EC=8B=9C=ED=97=98:=20=EC=A4=84=20?= =?UTF-8?q?=EC=84=B8=EC=9A=B0=EA=B8=B0=203=EA=B0=9C=20=C2=B7=20ABI=20?= =?UTF-8?q?=ED=8C=90=203=20(=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 --- integration_test/hwp_source_test.dart | 30 +++--- lib/app/app_controller.dart | 13 ++- lib/core/hwp_source.dart | 99 ++++++++++++++++--- lib/platform/android/hwp_pdf_ffi.dart | 75 ++++++++------- lib/ui/progress_dialog.dart | 3 +- test/hwp_abi_test.dart | 8 +- test/hwp_source_unit_test.dart | 133 +++++++++++++++++++++++--- tool/check_apk.ps1 | 2 +- tool/rhwp_ffi/src/lib.rs | 55 ++++++++--- 9 files changed, 323 insertions(+), 95 deletions(-) diff --git a/integration_test/hwp_source_test.dart b/integration_test/hwp_source_test.dart index 2356b4b..c56bb9f 100644 --- a/integration_test/hwp_source_test.dart +++ b/integration_test/hwp_source_test.dart @@ -23,28 +23,38 @@ class _FakeEngine implements HwpPdfEngine { @override Future open(String path, {String? password}) async { if (password == 'bad') throw const HwpPdfException(HwpErrorKind.passwordWrong, 'CryptoError WrongPassword'); - return _FakeDoc(this); + return QueuedHwpDoc(_FakeNative(this)); } } -class _FakeDoc implements HwpPdfDoc { +/// 앱과 같은 [QueuedHwpDoc] 아래의 가짜 C 함수 +class _FakeNative implements HwpNative { final _FakeEngine e; var closed = false; - _FakeDoc(this.e); + _FakeNative(this.e); @override int get pageCount => e.pages; @override - Future exportPdf(int from, int to, String out, {List fontDirs = const []}) async { + void cancel(int req) { + final h = e.hold; + if (h != null && !h.isCompleted) h.complete(); // Rust 처럼 다음 쪽에서 멈춤 + } + + @override + void cancelAll() => cancel(0); + + @override + Future<(int, String?)> export(int req, int from, int to, String fontDirs, String out) async { e.calls.add((from, to)); - // 끝나지 않는 변환 (시간 제한 시험): 풀려도 아무것도 쓰지 않는다 (시험이 끝난 뒤 폴더에 쓰지 않게) + // 끝나지 않는 변환 (시간 제한 시험): 멈춤으로 풀리면 아무것도 쓰지 않는다 (시험이 끝난 뒤 폴더에 쓰지 않게) if (e.hold != null) { await e.hold!.future; - throw const HwpPdfException(HwpErrorKind.cancelled, 'late'); + return (1, 'cancelled'); } if (e.failNext > 0) { e.failNext--; - throw const HwpPdfException(HwpErrorKind.render, 'page 1: boom'); + return (7, 'page 1: boom'); } final docs = []; for (var i = from; i < to; i++) { @@ -60,11 +70,9 @@ class _FakeDoc implements HwpPdfDoc { for (final d in docs) { await d.dispose(); } + return (0, null); } - @override - void cancel() {} - @override Future close() async => closed = true; } @@ -124,7 +132,7 @@ void main() { })); expect(err2, isA()); expect('$err2', contains('1~3')); - slow.hold!.complete(); + expect(slow.hold!.isCompleted, isTrue, reason: '시간 제한이 변환을 멈춤 (기다림만 끊지 않음)'); await tester.runAsync(s2!.dispose); // 열기 실패 (암호 등) 는 그대로 알린다 - 파일 탐색기는 다른 앱으로 연다 diff --git a/lib/app/app_controller.dart b/lib/app/app_controller.dart index 89a9bcb..a153aa4 100644 --- a/lib/app/app_controller.dart +++ b/lib/app/app_controller.dart @@ -214,11 +214,14 @@ class AppController extends ChangeNotifier { final gone = removeUpdateLeftovers(Directory.systemTemp.path); if (gone.isNotEmpty) _log(trf('지난 업데이트가 남긴 임시 파일을 지움: {0}', [gone.join(', ')])); } - // 관리자 지시 (10/20): 강제로 끝나 남은 HWP 보기 작업 폴더 (바꾼 PDF 들) - try { - final hwp = removeStaleHwpWork(await services.storage.tempDirectory()); - if (hwp.isNotEmpty) _log(trf('지난번에 남은 문서 보기 임시 폴더를 지움: {0}개', [hwp.length])); - } catch (_) {} + // 관리자 지시 (10/20): 강제로 끝나 남은 HWP 보기 작업 폴더 (바꾼 PDF 들). 앱 시작을 기다리게 하지 않는다 (28 지적: 파일 IO 를 + // 기다리면 시험의 가짜 시간 안에서 끝나지 않았다 - 시작을 늦출 이유도 없음) + unawaited(() async { + try { + final hwp = removeStaleHwpWork(await services.storage.tempDirectory()); + if (hwp.isNotEmpty) _log(trf('지난번에 남은 문서 보기 임시 폴더를 지움: {0}개', [hwp.length])); + } catch (_) {} + }()); ffmpegVersion = await _tool.version(); if (ffmpegVersion == null) { _log(tr('⚠ FFmpeg 를 찾을 수 없습니다. MKV 만들기를 사용할 수 없습니다.')); diff --git a/lib/core/hwp_source.dart b/lib/core/hwp_source.dart index 2a595e5..f2b7443 100644 --- a/lib/core/hwp_source.dart +++ b/lib/core/hwp_source.dart @@ -41,15 +41,93 @@ abstract class HwpPdfEngine { abstract class HwpPdfDoc { int get pageCount; - /// [from] 부터 [to] 앞까지 (0 부터) 를 PDF 파일 [out] 으로. [fontDirs] 는 글꼴을 찾을 폴더들 - Future exportPdf(int from, int to, String out, {List fontDirs = const []}); + /// [from] 부터 [to] 앞까지 (0 부터) 를 PDF 파일 [out] 으로. [fontDirs] 는 글꼴을 찾을 폴더들. + /// [timeout]: 바꾸기가 실제로 시작된 때부터 재고, 넘으면 그 요청만 멈추고 [HwpErrorKind.timeout] + Future exportPdf(int from, int to, String out, {List fontDirs = const [], Duration? timeout}); - /// 지금 바꾸는 것을 멈춘다 (시간 제한을 넘겼을 때 · 닫을 때 - 관리자 지시 10/20: 기다림만 끊으면 뒤에서 계속 돌아 - /// 다음 묶음이 줄을 서서 연달아 실패했다). 멈춘 바꾸기는 [HwpErrorKind.cancelled] 로 끝난다 - void cancel(); + /// 닫는다: 도는 바꾸기는 멈추고, 줄에 선 것은 시작하지 않는다 ([HwpErrorKind.closed]) Future close(); } +/// 변환기의 C 함수 (Android: libjj_rhwp.so - lib/platform/android/hwp_pdf_ffi.dart, 시험: 가짜). +/// 요청 번호 [export] 의 req 는 [cancel] 로 그 요청만 멈출 때 쓴다 +abstract class HwpNative { + int get pageCount; + + /// (오류 코드 - 0 이면 성공, 원문) + Future<(int, String?)> export(int req, int from, int to, String fontDirs, String out); + + /// 요청 [req] 만 멈춘다 (도는 중이면 다음 쪽에서, 시작 전이면 시작하자마자) + void cancel(int req); + + /// 닫기 전에 모두 멈춘다 + void cancelAll(); + Future close(); +} + +/// 관리자 지시 (10/20, 28 지적): 한 문서의 바꾸기를 한 번에 하나씩 줄 세우고, 시간 제한 · 멈춤을 요청마다 한다. +/// - 시간 제한은 바꾸기가 실제로 시작된 때부터 잰다 (예전에는 줄에 선 때부터 - 앞 묶음이 길면 뒤 묶음이 시작 전에 넘었다). +/// - 넘으면 그 요청만 멈춘다 (예전에는 문서 하나의 깃발이라 도는 앞 묶음이 멈췄다). 멈춘 바꾸기가 끝날 때까지 다음 것은 +/// 시작하지 않는다 (한 문서를 두 스레드가 함께 쓰지 않게). +/// - 닫으면 도는 것은 멈추고 줄에 선 것은 시작하지 않는다 (예전에는 시작할 때 깃발을 지워 닫기 요청이 사라졌다). +class QueuedHwpDoc implements HwpPdfDoc { + final HwpNative native; + QueuedHwpDoc(this.native); + + @override + int get pageCount => native.pageCount; + + var _seq = 0; + var _closed = false; + Future _queue = Future.value(); + Future? _closing; + + @override + Future exportPdf(int from, int to, String out, {List fontDirs = const [], Duration? timeout}) { + if (_closed) return Future.error(const HwpPdfException(HwpErrorKind.closed)); + final req = ++_seq; + final result = Completer(); + _queue = _queue.then((_) async { + if (_closed) { + result.completeError(const HwpPdfException(HwpErrorKind.closed)); + return; + } + Timer? timer; + if (timeout != null) { + timer = Timer(timeout, () { + if (result.isCompleted) return; + native.cancel(req); + result.completeError(HwpPdfException( + HwpErrorKind.timeout, 'timeout ${timeout.inSeconds}s (pages ${from + 1}~$to)', [from + 1, to, timeout.inSeconds])); + }); + } + try { + final (code, err) = await native.export(req, from, to, fontDirs.join('\n'), out); + if (!result.isCompleted) { + if (code == 0) { + result.complete(); + } else { + result.completeError(HwpPdfException(_closed ? HwpErrorKind.closed : hwpErrorKindOf(code), err ?? '')); + } + } + } catch (e, st) { + if (!result.isCompleted) result.completeError(e, st); + } finally { + timer?.cancel(); + } + }); + return result.future; + } + + @override + Future close() => _closing ??= () async { + _closed = true; + native.cancelAll(); + await _queue; // 도는 것이 멈춰 끝날 때까지 (줄에 선 것은 시작하지 않고 넘어감) + await native.close(); + }(); +} + /// 관리자 지시 (10/20, 28 지적): 변환기 오류의 종류 - 화면에는 종류에 맞는 번역 글, 원문은 [HwpPdfException.detail] ([자세히] · 작업 기록). /// 앞 9개는 tool/rhwp_ffi 의 JJ_RHWP_E_* 와 같은 순서 (1 부터) enum HwpErrorKind { cancelled, read, invalid, passwordRequired, passwordWrong, range, render, write, internal, timeout, closed } @@ -217,12 +295,8 @@ class HwpSource extends ReaderSource { if (!_made.contains(k) || !File(out).existsSync()) { final from = k * chunk, to = math.min(from + chunk, length); try { - await doc.exportPdf(from, to, out, fontDirs: fontDirs).timeout(timeout); - } on TimeoutException { - // 관리자 지시 (10/20): 기다림만 끊지 않고 변환도 멈춘다 (다음 묶음이 줄 서서 실패하지 않게) - doc.cancel(); - log?.call('HWP ${vBasename(path)}: timeout ${from + 1}~$to'); - throw HwpPdfException(HwpErrorKind.timeout, 'timeout ${timeout.inSeconds}s', [from + 1, to, timeout.inSeconds]); + // 시간 제한은 문서 ([QueuedHwpDoc]) 가 바꾸기가 시작된 때부터 재고, 넘으면 그 요청만 멈춘다 + await doc.exportPdf(from, to, out, fontDirs: fontDirs, timeout: timeout); } catch (e) { // 닫은 뒤에 끝난 변환은 (임시 폴더를 이미 지워 · 멈춰) 실패한다 - 그 이유 대신 "닫힌 문서" if (_disposed) throw StateError(tr('닫힌 문서입니다')); @@ -281,8 +355,7 @@ class HwpSource extends ReaderSource { final closing = f.then((c) => c.dispose(), onError: (Object _) {}); await Future.any([closing, Future.delayed(const Duration(milliseconds: 200))]); } - doc.cancel(); // 바꾸는 중이면 다음 쪽에서 멈춘다 - await doc.close(); // 멈춘 바꾸기가 끝난 뒤에 닫힌다 (엔진이 한 번에 하나씩) + 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 index 40e04ae..1bbc06c 100644 --- a/lib/platform/android/hwp_pdf_ffi.dart +++ b/lib/platform/android/hwp_pdf_ffi.dart @@ -7,8 +7,8 @@ import 'package:ffi/ffi.dart'; import '../../core/hwp_source.dart'; /// 관리자 지시 (10/20): Android 의 HWP · HWPX → PDF - APK 에 든 libjj_rhwp.so (rhwp v0.8.7, MIT - tool/build_rhwp_android.sh · -/// tool/rhwp_ffi) 를 dart:ffi 로 부른다. 여는 것 · 바꾸는 것은 다른 isolate 에서 (화면을 막지 않게), 한 문서는 한 번에 하나씩. -/// 라이브러리는 HWP 를 처음 열 때 읽는다 (앱을 켤 때 읽지 않음). +/// tool/rhwp_ffi) 를 dart:ffi 로 부른다. 여는 것 · 바꾸는 것은 다른 isolate 에서 (화면을 막지 않게). 줄 세우기 · 시간 제한 · +/// 멈춤은 [QueuedHwpDoc] 가 한다 (요청마다). 라이브러리는 HWP 를 처음 열 때 읽는다 (앱을 켤 때 읽지 않음). class RhwpEngine implements HwpPdfEngine { static const libName = 'libjj_rhwp.so'; @@ -16,18 +16,20 @@ class RhwpEngine implements HwpPdfEngine { /// 관리자 판단 (10/20): 한글 글꼴 컴포넌트를 받으면 그 폴더를 앞에 더한다 (아직 없음) static const fontDirs = ['/system/fonts']; - /// 이 Dart 가 기대하는 C ABI 의 판 (tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION 과 같아야 함) - static const abiVersion = 2; + /// 이 Dart 가 기대하는 C ABI 의 판 (tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION 과 같아야 함). + /// 3: 요청별 멈춤 (export 에 요청 번호 · jj_rhwp_cancel(번호) · jj_rhwp_cancel_all) + static const abiVersion = 3; /// 꼭 있어야 하는 C 함수 static const requiredSymbols = [ - 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_close', 'jj_rhwp_free', // + 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_cancel_all', // + 'jj_rhwp_close', 'jj_rhwp_free', ]; static bool? _available; /// 라이브러리를 읽을 수 있고 ABI 가 맞는지 (아니면 다른 앱으로 연다). 처음 HWP 를 열 때 한 번 본다. - /// 관리자 지시 (10/20, 28 지적): 옛 .so (판 1 - code 칸 · cancel 없음) 에 새 인수를 넘기면 힙이 망가지므로, 기호와 판이 맞을 때만 + /// 관리자 지시 (10/20, 28 지적): 옛 .so 에 새 인수를 넘기면 힙이 망가지므로, 기호와 판이 맞을 때만 static bool get available => _available ??= () { try { final lib = DynamicLibrary.open(libName); @@ -53,47 +55,41 @@ class RhwpEngine implements HwpPdfEngine { final (handle, code, err) = await Isolate.run(() => _open(path, password ?? '')); if (handle == 0) throw HwpPdfException(hwpErrorKindOf(code), err ?? ''); final pages = await Isolate.run(() => _pageCount(handle)); - return _RhwpDoc(handle, pages); + return QueuedHwpDoc(_RhwpNative(handle, pages)); } } -class _RhwpDoc implements HwpPdfDoc { +class _RhwpNative implements HwpNative { final int _handle; @override final int pageCount; var _closed = false; + _RhwpNative(this._handle, this.pageCount); - /// 한 번에 하나씩 (rhwp 문서는 여러 스레드에서 함께 바꾸지 않는다) - Future _queue = Future.value(); + @override + Future<(int, String?)> export(int req, int from, int to, String fontDirs, String out) { + final h = _handle; + return Isolate.run(() => _export(h, req, from, to, fontDirs, out)); + } - _RhwpDoc(this._handle, this.pageCount); - - Future _serial(Future Function() f) { - final run = _queue.then((_) => f()); - _queue = run.then((_) {}, onError: (Object _) {}); - return run; + /// 줄을 서지 않고 바로 (다른 isolate 의 바꾸기가 다음 쪽에서 끝난다) + @override + void cancel(int req) { + if (!_closed) _cancel(_handle, req); } @override - Future exportPdf(int from, int to, String out, {List fontDirs = const []}) => _serial(() async { - if (_closed) throw const HwpPdfException(HwpErrorKind.closed); - final h = _handle, dirs = fontDirs.join('\n'); - final (code, err) = await Isolate.run(() => _export(h, from, to, dirs, out)); - if (code != 0) throw HwpPdfException(hwpErrorKindOf(code), err ?? ''); - }); - - /// 지금 바꾸는 것을 멈춘다 (줄을 서지 않고 바로 - 다른 isolate 의 바꾸기가 다음 쪽에서 끝난다) - @override - void cancel() { - if (!_closed) _cancel(_handle); + void cancelAll() { + if (!_closed) _cancelAll(_handle); } + /// [QueuedHwpDoc.close] 가 도는 바꾸기가 끝난 뒤에 부른다 @override - Future close() => _serial(() async { - if (_closed) return; - _closed = true; - _close(_handle); - }); + Future close() async { + if (_closed) return; + _closed = true; + _close(_handle); + } } // ───────── C 함수 (isolate 안에서 부른다 - 핸들은 주소 숫자로 넘긴다) ───────── @@ -101,8 +97,10 @@ class _RhwpDoc implements HwpPdfDoc { typedef _OpenC = Pointer Function(Pointer, Pointer, Pointer, Pointer>); typedef _CountC = Int32 Function(Pointer); typedef _CountD = int Function(Pointer); -typedef _ExportC = Int32 Function(Pointer, Int32, Int32, Pointer, Pointer, Pointer, Pointer>); -typedef _ExportD = int Function(Pointer, int, int, Pointer, Pointer, Pointer, Pointer>); +typedef _ExportC = Int32 Function(Pointer, Uint64, Int32, Int32, Pointer, Pointer, Pointer, Pointer>); +typedef _ExportD = int Function(Pointer, int, int, int, Pointer, Pointer, Pointer, Pointer>); +typedef _CancelC = Void Function(Pointer, Uint64); +typedef _CancelD = void Function(Pointer, int); typedef _VoidC = Void Function(Pointer); typedef _VoidD = void Function(Pointer); typedef _FreeC = Void Function(Pointer); @@ -138,14 +136,14 @@ String? _takeError(DynamicLibrary lib, Pointer> err) { int _pageCount(int handle) => _lib().lookupFunction<_CountC, _CountD>('jj_rhwp_page_count')(Pointer.fromAddress(handle)); -(int, String?) _export(int handle, int from, int to, String fontDirs, String out) { +(int, String?) _export(int handle, int req, int from, int to, String fontDirs, String out) { final lib = _lib(); final export = lib.lookupFunction<_ExportC, _ExportD>('jj_rhwp_export_pdf'); final d = fontDirs.toNativeUtf8(), o = out.toNativeUtf8(); final code = calloc(); final err = calloc>(); try { - final r = export(Pointer.fromAddress(handle), from, to, d, o, code, err); + final r = export(Pointer.fromAddress(handle), req, from, to, d, o, code, err); final e = _takeError(lib, err); return r == 0 ? (0, null) : (code.value == 0 ? 9 : code.value, e); } finally { @@ -156,6 +154,9 @@ int _pageCount(int handle) => _lib().lookupFunction<_CountC, _CountD>('jj_rhwp_p } } -void _cancel(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_cancel')(Pointer.fromAddress(handle)); +void _cancel(int handle, int req) => + _lib().lookupFunction<_CancelC, _CancelD>('jj_rhwp_cancel')(Pointer.fromAddress(handle), req); + +void _cancelAll(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_cancel_all')(Pointer.fromAddress(handle)); void _close(int handle) => _lib().lookupFunction<_VoidC, _VoidD>('jj_rhwp_close')(Pointer.fromAddress(handle)); diff --git a/lib/ui/progress_dialog.dart b/lib/ui/progress_dialog.dart index e16337e..c6922d8 100644 --- a/lib/ui/progress_dialog.dart +++ b/lib/ui/progress_dialog.dart @@ -36,6 +36,7 @@ ProgressDialog showProgressDialog(BuildContext context, String text) { if (!open) return; byUser = false; final x = dctx; - if (x != null && x.mounted) Navigator.of(x).pop(); + // 그 창이 맨 위일 때만 (28 지적: 이론상 그 위에 다른 화면이 올라와 있으면 그것을 닫을 수 있다) + if (x != null && x.mounted && (ModalRoute.of(x)?.isCurrent ?? false)) Navigator.of(x).pop(); }); } diff --git a/test/hwp_abi_test.dart b/test/hwp_abi_test.dart index 4336139..8e6a355 100644 --- a/test/hwp_abi_test.dart +++ b/test/hwp_abi_test.dart @@ -8,14 +8,16 @@ import 'package:jj_mkvmaker/platform/android/hwp_pdf_ffi.dart'; void main() { const v1 = {'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_close', 'jj_rhwp_free', 'jj_rhwp_version'}; final v2 = {...v1, 'jj_rhwp_cancel', 'jj_rhwp_abi_version'}; + final v3 = {...v2, 'jj_rhwp_cancel_all'}; test('옛 .so (기호 없음) · 판이 다름 · 부르다 실패 → 쓰지 않음, 맞으면 씀', () { expect(RhwpEngine.abiCompatible(has: v1.contains, abi: () => throw ArgumentError('no symbol')), isFalse, reason: '판 1'); expect(RhwpEngine.abiCompatible(has: {...v1, 'jj_rhwp_abi_version'}.contains, abi: () => 2), isFalse, reason: 'cancel 없음'); - expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 1), isFalse, reason: '판이 다름'); - expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 3), isFalse, reason: '더 새 판도'); + expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => 2), isFalse, reason: '판 2 (cancel_all 없음 - 문서 하나의 멈춤 깃발)'); + expect(RhwpEngine.abiCompatible(has: v3.contains, abi: () => 2), isFalse, reason: '판이 다름'); + expect(RhwpEngine.abiCompatible(has: v3.contains, abi: () => 4), isFalse, reason: '더 새 판도'); expect(RhwpEngine.abiCompatible(has: (_) => throw StateError('x'), abi: () => 2), isFalse); - expect(RhwpEngine.abiCompatible(has: v2.contains, abi: () => RhwpEngine.abiVersion), isTrue); + expect(RhwpEngine.abiCompatible(has: v3.contains, abi: () => RhwpEngine.abiVersion), isTrue); }); test('Dart 의 판 = tool/rhwp_ffi 의 JJ_RHWP_ABI_VERSION · 꼭 있어야 하는 함수는 lib.rs 에 모두', () { diff --git a/test/hwp_source_unit_test.dart b/test/hwp_source_unit_test.dart index 862f276..1dbf0ac 100644 --- a/test/hwp_source_unit_test.dart +++ b/test/hwp_source_unit_test.dart @@ -6,35 +6,55 @@ import 'package:jj_mkvmaker/core/hwp_source.dart'; import 'package:jj_mkvmaker/core/reader_sources.dart'; import 'package:path/path.dart' as p; -/// 쪽 범위를 글로 적은 파일을 "PDF" 로 만드는 가짜 엔진 (rhwp 대신) - 부른 범위를 적어 둔다 +/// 쪽 범위를 글로 적은 파일을 "PDF" 로 만드는 가짜 엔진 (rhwp 대신) - 부른 범위를 적어 둔다. +/// 문서는 앱과 같은 [QueuedHwpDoc] (줄 세우기 · 시간 제한 · 멈춤) 위의 가짜 C 함수 ([_Native]) class _Engine implements HwpPdfEngine { final int pages; final calls = <(int, int)>[]; + + /// 있으면 바꾸기가 이것을 기다린다 (멈춤 · 닫기를 받으면 바로 끝남 - Rust 가 쪽마다 멈춤 표시를 보는 것처럼) Completer? hold; var closed = false; - var cancels = 0; + final cancelled = {}; + var cancelledAll = false; _Engine(this.pages); @override - Future open(String path, {String? password}) async => _Doc(this); + Future open(String path, {String? password}) async => QueuedHwpDoc(_Native(this)); } -class _Doc implements HwpPdfDoc { +class _Native implements HwpNative { final _Engine e; - _Doc(this.e); + _Native(this.e); @override int get pageCount => e.pages; @override - Future exportPdf(int from, int to, String out, {List fontDirs = const []}) async { + Future<(int, String?)> export(int req, int from, int to, String fontDirs, String out) async { e.calls.add((from, to)); final h = e.hold; if (h != null) await h.future; + if (e.cancelledAll || e.cancelled.contains(req)) return (1, 'cancelled'); File(out).writeAsStringSync('$from-$to'); + return (0, null); + } + + void _wake() { + final h = e.hold; + if (h != null && !h.isCompleted) h.complete(); } @override - void cancel() => e.cancels++; + void cancel(int req) { + e.cancelled.add(req); + _wake(); + } + + @override + void cancelAll() { + e.cancelledAll = true; + _wake(); + } @override Future close() async => e.closed = true; @@ -151,10 +171,9 @@ void main() { expect(err, isA()); expect((err! as HwpPdfException).kind, HwpErrorKind.timeout); expect('$err', allOf(contains('1~5'), contains('0초'))); - expect(e.cancels, 1, reason: '시간 제한을 넘기면 변환도 멈춘다 (기다림만 끊지 않음)'); + expect(e.cancelled, {1}, reason: '시간 제한을 넘기면 그 요청의 변환도 멈춘다 (기다림만 끊지 않음)'); expect(s.openChunks, isNot(contains(0)), reason: '실패한 묶음은 기억하지 않음 - [다시 시도] 가 다시 만든다'); - e.hold!.complete(); - e.hold = null; + e.hold = null; // 멈춤이 기다림을 깨웠다 await s.load(0, maxWidth: 100); expect(e.calls.where((c) => c == (0, 5)).length, greaterThanOrEqualTo(2)); await s.dispose(); @@ -166,9 +185,9 @@ void main() { final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, opener: o.call); final pending = s.load(0, maxWidth: 100).then((_) => null, onError: (Object x) => x); await settle(); - await s.dispose(); // 기다리지 않고 바로 끝난다 - expect(e.cancels, 1, reason: '닫으면 바꾸던 것을 멈춘다'); - e.hold!.complete(); + await s.dispose(); // 도는 바꾸기는 멈춤으로 곧 끝나고, 닫힌다 + expect(e.cancelledAll, isTrue, reason: '닫으면 바꾸던 것을 멈춘다'); + expect(e.closed, isTrue); final r = await pending; expect(r, isA(), reason: '닫힌 문서'); expect(o.opened, isEmpty, reason: '닫은 뒤에는 PDF 를 열지 않음'); @@ -206,4 +225,92 @@ void main() { expect(rs, contains('pub const JJ_RHWP_E_$n: c_int = ${i + 1};'), reason: n); } }); + + group('QueuedHwpDoc (28 지적: 시간 제한 · 멈춤은 요청마다)', () { + test('앞 묶음이 오래 돌아도, 줄에서 기다린 뒤 묶음의 시간 제한은 제 시작부터 잰다', () async { + final n = _Gated(); + final d = QueuedHwpDoc(n); + final a = d.exportPdf(0, 5, 'a', timeout: const Duration(seconds: 30)); // 오래 도는 앞 묶음 + final b = d.exportPdf(5, 10, 'b', timeout: const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 700)); // 뒤 묶음의 제한보다 오래 줄에서 기다림 + expect(n.started, [1]); + n.release(1); + await a; + await Future.delayed(const Duration(milliseconds: 50)); + expect(n.started, [1, 2]); + n.release(2); + await b; // 시간 제한 없이 끝남 + expect(n.cancelled, isEmpty); + }); + + test('한 요청이 시간을 넘기면 그 요청만 멈추고, 멈춘 것이 끝난 뒤 다음 것이 돈다', () async { + final n = _Gated(); + final d = QueuedHwpDoc(n); + Object? err; + final a = d.exportPdf(0, 5, 'a', timeout: const Duration(milliseconds: 200)).catchError((Object e) => err = e); + final b = d.exportPdf(5, 10, 'b'); + await a; + expect(err, isA().having((e) => e.kind, 'kind', HwpErrorKind.timeout)); + expect(n.cancelled, {1}, reason: '다른 요청 (2) 은 멈추지 않음'); + await Future.delayed(const Duration(milliseconds: 50)); + expect(n.started, [1, 2], reason: '멈춘 1 이 끝난 뒤에 2 가 시작'); + n.release(2); + await b; + }); + + test('닫으면 도는 것은 멈추고 줄에 선 것은 시작하지 않는다 (닫기 요청이 지워지지 않음)', () async { + final n = _Gated(); + final d = QueuedHwpDoc(n); + final a = d.exportPdf(0, 5, 'a').then((_) => null, onError: (Object e) => e); + final b = d.exportPdf(5, 10, 'b').then((_) => null, onError: (Object e) => e); + await Future.delayed(const Duration(milliseconds: 50)); + await d.close(); + expect(n.all, isTrue); + expect(n.started, [1], reason: '줄에 선 2 는 시작하지 않음'); + expect(n.closed, isTrue); + expect(await a, isA().having((e) => e.kind, 'kind', HwpErrorKind.closed)); + expect(await b, isA().having((e) => e.kind, 'kind', HwpErrorKind.closed)); + expect(() => d.exportPdf(0, 1, 'c'), throwsA(isA()), reason: '닫은 뒤의 요청'); + }); + }); +} + +/// 요청마다 문이 있는 가짜 C 함수 - 멈춤 · 닫기를 받으면 문이 열리고 (cancelled) 로 끝난다 +class _Gated implements HwpNative { + final started = []; + final gates = >{}; + final cancelled = {}; + var all = false; + var closed = false; + @override + int get pageCount => 10; + + Completer _gate(int req) => gates.putIfAbsent(req, Completer.new); + void release(int req) { + if (!_gate(req).isCompleted) _gate(req).complete(); + } + + @override + Future<(int, String?)> export(int req, int from, int to, String fontDirs, String out) async { + started.add(req); + await _gate(req).future; + return all || cancelled.contains(req) ? (1, 'cancelled') : (0, null); + } + + @override + void cancel(int req) { + cancelled.add(req); + release(req); + } + + @override + void cancelAll() { + all = true; + for (final r in [...gates.keys, ...started]) { + release(r); + } + } + + @override + Future close() async => closed = true; } diff --git a/tool/check_apk.ps1 b/tool/check_apk.ps1 index 8a0763f..e73f9c9 100644 --- a/tool/check_apk.ps1 +++ b/tool/check_apk.ps1 @@ -77,7 +77,7 @@ if ($rhwpSo) { $nm = Get-ChildItem (Join-Path $app '..\tools\android-sdk\ndk') -Recurse -Filter llvm-nm.exe -ErrorAction SilentlyContinue | Select-Object -Last 1 if (-not $nm) { throw 'llvm-nm.exe 를 찾지 못했습니다 (tools\android-sdk\ndk)' } $syms = @(& $nm.FullName -D --defined-only $rhwpSo 2>$null | ForEach-Object { ($_ -split '\s+')[-1] }) - $need = 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_close', 'jj_rhwp_free' + $need = 'jj_rhwp_abi_version', 'jj_rhwp_open', 'jj_rhwp_page_count', 'jj_rhwp_export_pdf', 'jj_rhwp_cancel', 'jj_rhwp_cancel_all', 'jj_rhwp_close', 'jj_rhwp_free' $miss = @($need | Where-Object { $syms -notcontains $_ }) if ($miss) { $bad += "libjj_rhwp.so 가 옛 C ABI 입니다 - 없는 함수: $($miss -join ', ') (sh tool/build_rhwp_android.sh 로 다시 빌드)" } else { $rhwpNote = " · libjj_rhwp.so 함수 $($need.Count)개 있음" } diff --git a/tool/rhwp_ffi/src/lib.rs b/tool/rhwp_ffi/src/lib.rs index d61fb71..1031d48 100644 --- a/tool/rhwp_ffi/src/lib.rs +++ b/tool/rhwp_ffi/src/lib.rs @@ -2,14 +2,18 @@ //! 관리자 지시 (10/20): Android 에서 HWP · HWPX 를 앱의 보기 화면 (PDF) 으로 연다 - 문서를 한 번 열고, 쪽 범위마다 PDF 를 만든다 //! (큰 문서는 앞쪽부터 몇 쪽씩 보이면서 뒤를 이어 만든다). Dart 는 dart:ffi 로 부른다 (lib/platform/android/hwp_pdf_ffi.dart). //! - 오류는 종류 코드 (JJ_RHWP_E_*) 로 돌려주고, 원문 (rhwp 의 글) 은 err 로 (화면은 코드로 고른 번역 글, 원문은 [자세히] · 작업 기록). -//! - jj_rhwp_cancel: 다른 스레드에서 지금 바꾸는 것을 멈춘다 (쪽마다 확인 - 관리자 지시 10/20, 시간 제한 뒤에도 계속 돌지 않게). +//! - jj_rhwp_cancel(요청 번호): 다른 스레드에서 그 요청만 멈춘다 (쪽마다 확인 - 관리자 지시 10/20, 시간 제한 뒤에도 계속 돌지 않게). +//! jj_rhwp_cancel_all: 닫을 때 - 도는 것 · 앞으로 올 것 모두 멈춘다 (ABI 3: 예전에는 문서 하나의 깃발이라 한 요청의 시간 제한이 +//! 앞 묶음까지 멈췄고, 바꾸기를 시작할 때 깃발을 지워 닫기 요청이 사라졌다 - 28 지적). //! 오류 글 · 돌려준 글은 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 std::collections::HashSet; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; use rhwp::document_core::DocumentCore; use rhwp::paint::RenderProfile; @@ -39,7 +43,16 @@ pub const JJ_RHWP_E_INTERNAL: c_int = 9; /// 연 문서 pub struct JjRhwpDoc { core: DocumentCore, - cancel: AtomicBool, + /// 멈추라고 한 요청 번호 (끝나면 지운다) + cancelled: Mutex>, + /// 닫는 중 - 모든 요청을 멈춘다 (되돌리지 않는다) + closing: AtomicBool, +} + +impl JjRhwpDoc { + fn stopped(&self, req: u64) -> bool { + self.closing.load(Ordering::SeqCst) || self.cancelled.lock().map(|s| s.contains(&req)).unwrap_or(false) + } } struct Fail(c_int, String); @@ -103,7 +116,7 @@ fn open_kind(msg: &str, with_password: bool) -> c_int { /// 이 C ABI 의 판 - Dart (lib/platform/android/hwp_pdf_ffi.dart 의 RhwpEngine.abiVersion) 와 같아야 앱 안 보기를 쓴다 /// (관리자 지시 10/20, 28 지적: 옛 .so (판 1 - code 칸 · cancel 없음) 에 새 인수를 넘기면 힙이 망가진다). /// 함수의 인수 · 오류 코드를 바꾸면 올린다 -pub const JJ_RHWP_ABI_VERSION: c_int = 2; +pub const JJ_RHWP_ABI_VERSION: c_int = 3; #[no_mangle] pub extern "C" fn jj_rhwp_abi_version() -> c_int { @@ -136,7 +149,7 @@ pub extern "C" fn jj_rhwp_open( let msg = e.to_string(); Fail(open_kind(&msg, pw.is_some()), msg) })?; - Ok(Box::into_raw(Box::new(JjRhwpDoc { core, cancel: AtomicBool::new(false) }))) + Ok(Box::into_raw(Box::new(JjRhwpDoc { core, cancelled: Mutex::new(HashSet::new()), closing: AtomicBool::new(false) }))) }) } @@ -150,22 +163,33 @@ pub extern "C" fn jj_rhwp_page_count(doc: *const JjRhwpDoc) -> c_int { catch_unwind(AssertUnwindSafe(|| doc.core.page_count() as c_int)).unwrap_or(-1) } -/// 지금 바꾸는 것을 멈춘다 (다른 스레드에서 불러도 됨 - 다음 쪽에서 JJ_RHWP_E_CANCELLED 로 끝난다). -/// 다음 jj_rhwp_export_pdf 는 표시를 지우고 시작한다 +/// 요청 [req] 만 멈춘다 (다른 스레드에서 불러도 됨 - 도는 중이면 다음 쪽에서, 아직 시작 전이면 시작하자마자 +/// JJ_RHWP_E_CANCELLED 로 끝난다). 다른 요청은 그대로 #[no_mangle] -pub extern "C" fn jj_rhwp_cancel(doc: *const JjRhwpDoc) { +pub extern "C" fn jj_rhwp_cancel(doc: *const JjRhwpDoc, req: u64) { if !doc.is_null() { - unsafe { &*doc }.cancel.store(true, Ordering::SeqCst); + if let Ok(mut s) = unsafe { &*doc }.cancelled.lock() { + s.insert(req); + } } } -/// [from, to) 쪽 (0 부터) 을 PDF 파일 out 으로. font_dirs 는 줄바꿈 (\n) 으로 나눈 글꼴 폴더들 (Android 는 시스템 글꼴을 +/// 닫기 전에: 도는 요청 · 앞으로 올 요청 모두 멈춘다 (되돌리지 않음) +#[no_mangle] +pub extern "C" fn jj_rhwp_cancel_all(doc: *const JjRhwpDoc) { + if !doc.is_null() { + unsafe { &*doc }.closing.store(true, Ordering::SeqCst); + } +} + +/// 요청 [req] (부르는 쪽이 정한 번호 - jj_rhwp_cancel 로 멈출 때 씀): [from, to) 쪽 (0 부터) 을 PDF 파일 out 으로. font_dirs 는 줄바꿈 (\n) 으로 나눈 글꼴 폴더들 (Android 는 시스템 글꼴을 /// 스스로 찾지 않으므로 /system/fonts 와 앱이 받은 한글 글꼴 폴더를 넘긴다). 성공 0, 실패 -1 과 code · err. /// rhwp 의 render_pages_pdf_native_with_options 와 같게 (Print 로 쪽마다 SVG → 한 PDF) 하되, 쪽마다 멈춤 표시를 본다 /// (하이퍼링크는 넣지 않는다 - 보기용) #[no_mangle] pub extern "C" fn jj_rhwp_export_pdf( doc: *const JjRhwpDoc, + req: u64, from: c_int, to: c_int, font_dirs: *const c_char, @@ -178,7 +202,16 @@ pub extern "C" fn jj_rhwp_export_pdf( return Err(Fail(JJ_RHWP_E_RANGE, "no document".into())); } let doc = unsafe { &*doc }; - doc.cancel.store(false, Ordering::SeqCst); + // 끝나면 (어떻게 끝나든) 이 요청의 멈춤 표시를 지운다 - 시작할 때는 지우지 않는다 (먼저 온 멈춤 · 닫기를 지키려고) + struct Forget<'a>(&'a JjRhwpDoc, u64); + impl Drop for Forget<'_> { + fn drop(&mut self) { + if let Ok(mut s) = self.0.cancelled.lock() { + s.remove(&self.1); + } + } + } + let _forget = Forget(doc, req); let count = doc.core.page_count() as c_int; if from < 0 || to > count || from >= to { return Err(Fail(JJ_RHWP_E_RANGE, format!("pages {from}..{to} / {count}"))); @@ -192,7 +225,7 @@ pub extern "C" fn jj_rhwp_export_pdf( .filter(|s| !s.is_empty()) .map(PathBuf::from) .collect(); - let cancelled = || doc.cancel.load(Ordering::SeqCst); + let cancelled = || doc.stopped(req); let mut svgs = Vec::with_capacity((to - from) as usize); for page in from as u32..to as u32 { if cancelled() { From 6b4743c9e9fee23e0e1c192a885e4700d112df53 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:05:47 +0900 Subject: [PATCH 10/24] =?UTF-8?q?WIP=20=EB=B3=B4=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20[=EB=8B=A4=EC=8B=9C=20=EC=8B=9C=EB=8F=84]=20(28=20?= =?UTF-8?q?=EC=A7=80=EC=A0=81,=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80?= =?UTF-8?q?=EC=8B=9C=2010/20):=20=EA=B7=B8=EB=A6=BC=20=C2=B7=20ZIP=20?= =?UTF-8?q?=EC=9B=90=EB=B3=B8=EB=8F=84=20=EB=8B=A4=EC=8B=9C=20=EC=9D=BD?= =?UTF-8?q?=EC=9D=8C=20-=20=EA=B7=B8=20=EC=9E=A5=EC=9D=98=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=ED=82=A4=EB=A5=BC=20=EC=98=AC=EB=A6=AC=EA=B3=A0=20?= =?UTF-8?q?(=EA=B0=99=EC=9D=80=20=ED=82=A4=20=C2=B7=20=EA=B0=99=EC=9D=80?= =?UTF-8?q?=20=EA=B7=B8=EB=A6=BC=EC=9D=B4=EB=A9=B4=20Image=20=EA=B0=80=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=9D=BD=EC=A7=80=20=EC=95=8A=EC=95=98?= =?UTF-8?q?=EB=8B=A4)=20=EA=B7=B8=EB=A6=BC=20=EC=BA=90=EC=8B=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=8F=84=20=EB=B9=84=EC=9B=80=20=C2=B7=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=BC=20=EB=94=94=EC=BD=94=EB=93=9C=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=EB=8A=94=20"=EA=B7=B8=EB=A6=BC=20=ED=8C=8C=EC=9D=BC=EC=9D=84?= =?UTF-8?q?=20=EC=9D=BD=EC=9D=84=20=EC=88=98=20=EC=97=86=EC=8A=B5=EB=8B=88?= =?UTF-8?q?=EB=8B=A4=20-=20=EA=B9=A8=EC=A1=8C=EA=B1=B0=EB=82=98=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D",=20=EC=9B=90=EB=AC=B8=EC=9D=80=20[=EC=9E=90?= =?UTF-8?q?=EC=84=B8=ED=9E=88]=20=C2=B7=20=EC=8B=9C=ED=97=98:=20=EA=B9=A8?= =?UTF-8?q?=EC=A7=84=20=EA=B7=B8=EB=A6=BC=20=E2=86=92=20=EA=B3=A0=EC=B9=9C?= =?UTF-8?q?=20=EB=92=A4=20[=EB=8B=A4=EC=8B=9C=20=EC=8B=9C=EB=8F=84]=20?= =?UTF-8?q?=ED=95=98=EB=A9=B4=20=EC=97=B4=EB=A6=BC=20(=ED=82=A4=20?= =?UTF-8?q?=EC=98=AC=EB=A6=AC=EA=B8=B0=EB=A5=BC=20=EB=B9=BC=EB=A9=B4=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=ED=95=A8=EC=9D=84=20=ED=99=95=EC=9D=B8)=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 --- assets/l10n/en.json | 1 + assets/l10n/ja.json | 1 + assets/l10n/zh-Hans.json | 1 + lib/ui/reader_page.dart | 71 +++++++++++++++++++++++++--------------- test/reader_test.dart | 28 ++++++++++++++++ 5 files changed, 75 insertions(+), 27 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index c192a1d..d2928b8 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -800,6 +800,7 @@ "그림 · 만화 보기, PDF 보기 · 페이지 편집, ZIP 목록 · 만화 보기": "Images · comic reading, PDF view · page editing, ZIP list · comic reading", "그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})": "Images and prompts leave this device ({0})", "그림 보기 대상": "Image viewer file types", + "그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식": "Can't read the image file - damaged or unsupported format", "그림으로 바꾸는 중": "Turning into an image", "그림을 만들지 못했습니다: {0}": "Couldn't make the image: {0}", "그림을 바꿔 만들기": "Make from this picture", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index b6c6095..be1d869 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -800,6 +800,7 @@ "그림 · 만화 보기, PDF 보기 · 페이지 편집, ZIP 목록 · 만화 보기": "画像・マンガ表示、PDF 表示・ページ編集、ZIP 一覧・マンガ表示", "그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})": "画像・プロンプトが端末の外に送られます ({0})", "그림 보기 대상": "画像表示の対象", + "그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식": "画像ファイルを読み込めません - 破損しているか未対応の形式", "그림으로 바꾸는 중": "画像に変換中", "그림을 만들지 못했습니다: {0}": "画像を作成できませんでした: {0}", "그림을 바꿔 만들기": "画像を変えて作成", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index b1d77b7..37d1822 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -800,6 +800,7 @@ "그림 · 만화 보기, PDF 보기 · 페이지 편집, ZIP 목록 · 만화 보기": "图片 · 漫画阅读,PDF 查看 · 页面编辑,ZIP 列表 · 漫画阅读", "그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})": "图片和提示词将离开本设备 ({0})", "그림 보기 대상": "图片查看的文件类型", + "그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식": "无法读取图片文件 - 已损坏或格式不受支持", "그림으로 바꾸는 중": "正在生成图像", "그림을 만들지 못했습니다: {0}": "无法生成图片: {0}", "그림을 바꿔 만들기": "基于图片生成", diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index e9601e3..fa450e6 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -68,6 +68,9 @@ class _ReaderPageState extends State { /// PDF 는 페이지를 지우거나 넣으면 다시 그린다 int _version = 0; + /// [다시 시도] 를 누른 횟수 (장마다) - 그 장의 화면을 새로 만든다 (28 지적: 같은 키면 실패한 그림을 그대로 다시 썼다) + final _retry = {}; + @override void initState() { super.initState(); @@ -442,37 +445,51 @@ class _ReaderPageState extends State { final slot = _v[i]; final half = slot.part >= 0; // 관리자 지시 (10/20, HWP 2단계): 이유와 [다시 시도] - 그 장의 기억을 지우고 다시 부른다 (시간 제한을 넘긴 HWP 묶음 등) - Widget failed(Object? e) => SizedBox( - width: 320, - height: 160, - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(trf('열 수 없습니다: {0}', [e]), textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70)), - const SizedBox(height: 8), - // 관리자 지시 (10/20, 28 지적): 변환기 원문은 화면 글 대신 [자세히] 에 - if (e is HwpPdfException && e.detail.isNotEmpty) - TextButton( - onPressed: () => showDialog( - context: context, - builder: (ctx) => AlertDialog( - content: SelectableText(e.detail, style: const TextStyle(fontSize: 12)), - actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(tr('닫기')))], + // [provider]: 그림 파일 · ZIP 의 그림을 읽다 (디코드) 실패한 것 - 화면 글은 종류에 맞게, 원문은 [자세히] (28 지적) + Widget failed(Object? e, {ImageProvider? provider}) { + final text = e is HwpPdfException + ? trf('열 수 없습니다: {0}', [e.message]) + : provider != null + ? tr('그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식') + : trf('열 수 없습니다: {0}', [e]); + final detail = e is HwpPdfException ? e.detail : (provider != null ? '$e' : ''); + return SizedBox( + width: 320, + height: 160, + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text(text, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70)), + const SizedBox(height: 8), + // 관리자 지시 (10/20, 28 지적): 변환기 · 그림 디코드 원문은 화면 글 대신 [자세히] 에 + if (detail.isNotEmpty) + TextButton( + onPressed: () => showDialog( + context: context, + builder: (ctx) => AlertDialog( + content: SelectableText(detail, style: const TextStyle(fontSize: 12)), + actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(tr('닫기')))], + ), ), + child: Text(tr('자세히')), ), - child: Text(tr('자세히')), + OutlinedButton( + onPressed: () { + // 그림은 Flutter 의 그림 캐시에 실패가 남아 같은 키로 다시 쓰인다 - 비우고 (ResizeImage 의 키째) 다시 읽는다 + if (provider != null) unawaited(provider.evict()); + setState(() { + _cache.remove(slot.page); + _cacheHalf.remove(slot.page); + _sharp.remove(i); + _sharpWidth.remove(i); + _retry[slot.page] = (_retry[slot.page] ?? 0) + 1; + }); + }, + child: Text(tr('다시 시도')), ), - OutlinedButton( - onPressed: () => setState(() { - _cache.remove(slot.page); - _cacheHalf.remove(slot.page); - _sharp.remove(i); - _sharpWidth.remove(i); - }), - child: Text(tr('다시 시도')), - ), - ])); + ])); + } // 확대해 다시 그린 것이 있으면 그것 (다 그릴 때까지는 앞의 그림을 그대로 보인다) final page = FutureBuilder( - key: ValueKey('$_version:${slot.page}:${slot.part}'), + key: ValueKey('$_version:${slot.page}:${slot.part}:${_retry[slot.page] ?? 0}'), future: _sharp[i] ?? (half ? _loadHalf(slot.page) : _load(slot.page)), builder: (context, snap) { if (snap.hasError) return Center(child: failed(snap.error)); @@ -481,7 +498,7 @@ class _ReaderPageState extends State { Widget picture = r.image != null ? RawImage(image: r.image, filterQuality: FilterQuality.medium) : Image(image: r.provider!, gaplessPlayback: true, filterQuality: FilterQuality.medium, - errorBuilder: (_, e, _) => failed(e)); + errorBuilder: (_, e, _) => failed(e, provider: r.provider)); // 120: 회전 · 상하 반전 (보기만, 파일은 그대로) if (_turns != 0) picture = RotatedBox(quarterTurns: _turns, child: picture); if (_flipV) picture = Transform.flip(flipY: true, child: picture); diff --git a/test/reader_test.dart b/test/reader_test.dart index 7a43932..d79e976 100644 --- a/test/reader_test.dart +++ b/test/reader_test.dart @@ -188,6 +188,33 @@ void main() { expect(c.settings.readerSystemBars, isFalse); expect(AppSettings.fromJson(c.settings.toJson()).readerSystemBars, isFalse); }); + + testWidgets('그림이 깨졌으면 화면 글 + [자세히], 파일을 고친 뒤 [다시 시도] 하면 열린다 (28 지적: 같은 키 · 같은 그림이라 Image 가 다시 읽지 않았다 - 장의 키를 올리고 그림 캐시도 비움)', (tester) async { + tester.view.physicalSize = const Size(900, 1200); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final broken = File(p.join(tmp.path, 'broken.png'))..writeAsBytesSync([1, 2, 3, 4, 5]); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + final src = ImageFilesSource([broken.path], tempDir: tmp.path, title: 'imgs'); + Future settle() async { + for (var k = 0; k < 20; k++) { + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 50))); + await tester.pump(); + } + } + + await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: src))); + await settle(); + expect(find.text('그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식'), findsOneWidget, reason: '원문 대신 화면 글'); + expect(find.text('자세히'), findsOneWidget, reason: '원문은 [자세히] 에'); + // 파일을 고치고 [다시 시도] + broken.writeAsBytesSync(_png(0, 200, 0)); + await tester.tap(find.text('다시 시도')); + await settle(); + expect(find.text('그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식'), findsNothing); + expect(find.byType(RawImage), findsWidgets, reason: '고친 그림이 보임'); + await tester.pumpWidget(const SizedBox()); + }); } /// 읽은 너비를 기록 (확대하면 더 크게 다시 읽는지) @@ -209,4 +236,5 @@ class _CountingSource implements ReaderSource { @override Future dispose() => inner.dispose(); + } From dc77998e634ff560c9008353e14b474aebf08b72 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:23:40 +0900 Subject: [PATCH 11/24] =?UTF-8?q?WIP=20X-plore=20=EB=A7=9E=EC=B6=94?= =?UTF-8?q?=EA=B8=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20):=20=ED=8F=B4=EB=8D=94=20=EA=B8=B8=EA=B2=8C=20?= =?UTF-8?q?=EB=88=84=EB=A5=B4=EA=B8=B0=20[=EC=82=AC=EC=A7=84=20=EC=8A=AC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=93=9C=EC=87=BC]=20-=20=EA=B7=B8=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EC=9D=98=20=EA=B7=B8=EB=A6=BC=20(=EC=A7=80?= =?UTF-8?q?=EA=B8=88=20=EC=A0=95=EB=A0=AC=20=C2=B7=20=EC=88=A8=EA=B8=B4=20?= =?UTF-8?q?=EA=B2=83=20=EB=B9=BC=EA=B3=A0)=20=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=97=B4?= =?UTF-8?q?=EA=B3=A0=20[=EA=B3=84=EC=86=8D=20=EB=B3=B4=EA=B8=B0]=20(117)?= =?UTF-8?q?=20=EB=A5=BC=20=EC=BC=A0=20=EC=B1=84=20=EC=8B=9C=EC=9E=91,=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=BC=EC=9D=B4=20=EC=97=86=EC=9C=BC=EB=A9=B4=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=C2=B7=20=EC=8B=9C=ED=97=98=20(=EA=B2=80?= =?UTF-8?q?=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 --- assets/l10n/en.json | 3 +++ assets/l10n/ja.json | 3 +++ assets/l10n/zh-Hans.json | 3 +++ lib/ui/explorer_page.dart | 26 ++++++++++++++++++++++++++ lib/ui/reader_page.dart | 10 +++++++--- test/explorer_test.dart | 27 +++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 3 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index d2928b8..ccfbbdd 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1521,6 +1521,7 @@ "사용자가 취소했습니다.": "Cancelled by the user.", "사용할 수 있는 자막 사이트가 없습니다.": "No subtitle site is available.", "사진": "Photos", + "사진 슬라이드쇼": "Photo slideshow", "사진에는 '사진' 모델이 더 낫지만, 이 기기 (그래픽카드 없음) 에서는 한 장에 약 20분 걸립니다.": "The 'Photos' model is better for photos, but on this device (no graphics card) it takes about 20 minutes per image.", "사진용": "Photos", "삭제": "Delete", @@ -2103,6 +2104,7 @@ "이 폴더에 현재 페이지 추가": "Add the current page to this folder", "이 폴더에는 고를 파일이 없습니다": "There are no files to choose in this folder", "이 폴더에는 그림 · 동영상이 없습니다": "No pictures or videos in this folder", + "이 폴더에는 그림이 없습니다": "There are no images in this folder", "이 폴더에만: {0}": "This folder only: {0}", "이 폴더의 동영상 재생": "Play videos in this folder", "이 폴더의 파일": "Files in this folder", @@ -2734,6 +2736,7 @@ "폴더는 보여도 그 안의 동영상 · 파일은 이 권한이 있어야 보입니다. 허용 화면에서 JJ_MKVMaker 를 켜고 돌아오세요.": "Folders are shown, but the videos and files inside need this permission. Turn on JJ_MKVMaker on the permission screen and come back.", "폴더를 만들 수 없습니다: {0}": "Cannot create the folder: {0}", "폴더를 읽는 중…": "Reading folders…", + "폴더를 읽을 수 없습니다: {0}": "Can't read the folder: {0}", "폴더를 자기 안으로 복사 · 이동할 수 없습니다": "Cannot copy or move a folder into itself", "폴더를 자기 안으로 복사할 수 없습니다": "Cannot copy a folder into itself", "폴더를 자기 안으로 옮길 수 없습니다": "Cannot move a folder into itself", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index be1d869..c8877da 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1521,6 +1521,7 @@ "사용자가 취소했습니다.": "ユーザーがキャンセルしました。", "사용할 수 있는 자막 사이트가 없습니다.": "使用できる字幕サイトがありません。", "사진": "写真", + "사진 슬라이드쇼": "写真スライドショー", "사진에는 '사진' 모델이 더 낫지만, 이 기기 (그래픽카드 없음) 에서는 한 장에 약 20분 걸립니다.": "写真には「写真」モデルが適していますが、この端末 (グラフィックカードなし) では 1 枚に約 20 分かかります。", "사진용": "写真用", "삭제": "削除", @@ -2103,6 +2104,7 @@ "이 폴더에 현재 페이지 추가": "このフォルダーに現在のページを追加", "이 폴더에는 고를 파일이 없습니다": "このフォルダーには選べるファイルがありません", "이 폴더에는 그림 · 동영상이 없습니다": "このフォルダーには画像・動画がありません", + "이 폴더에는 그림이 없습니다": "このフォルダーには画像がありません", "이 폴더에만: {0}": "このフォルダーだけ: {0}", "이 폴더의 동영상 재생": "このフォルダーの動画を再生", "이 폴더의 파일": "このフォルダーのファイル", @@ -2734,6 +2736,7 @@ "폴더는 보여도 그 안의 동영상 · 파일은 이 권한이 있어야 보입니다. 허용 화면에서 JJ_MKVMaker 를 켜고 돌아오세요.": "フォルダーは見えても、中の動画・ファイルはこの権限が必要です。許可画面で JJ_MKVMaker をオンにして戻ってください。", "폴더를 만들 수 없습니다: {0}": "フォルダーを作成できません: {0}", "폴더를 읽는 중…": "フォルダーを読み込み中…", + "폴더를 읽을 수 없습니다: {0}": "フォルダーを読み込めません: {0}", "폴더를 자기 안으로 복사 · 이동할 수 없습니다": "フォルダーを自分自身の中にコピー · 移動できません", "폴더를 자기 안으로 복사할 수 없습니다": "フォルダーを自分自身の中にコピーできません", "폴더를 자기 안으로 옮길 수 없습니다": "フォルダーを自分自身の中に移動できません", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 37d1822..f1f3c36 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1521,6 +1521,7 @@ "사용자가 취소했습니다.": "用户已取消。", "사용할 수 있는 자막 사이트가 없습니다.": "没有可用的字幕网站。", "사진": "照片", + "사진 슬라이드쇼": "照片幻灯片", "사진에는 '사진' 모델이 더 낫지만, 이 기기 (그래픽카드 없음) 에서는 한 장에 약 20분 걸립니다.": "照片更适合使用“照片”模型,但在本设备 (无显卡) 上每张约需 20 分钟。", "사진용": "照片", "삭제": "删除", @@ -2103,6 +2104,7 @@ "이 폴더에 현재 페이지 추가": "将当前页面添加到此文件夹", "이 폴더에는 고를 파일이 없습니다": "此文件夹中没有可选择的文件", "이 폴더에는 그림 · 동영상이 없습니다": "此文件夹中没有图片或视频", + "이 폴더에는 그림이 없습니다": "此文件夹中没有图片", "이 폴더에만: {0}": "仅此文件夹: {0}", "이 폴더의 동영상 재생": "播放此文件夹中的视频", "이 폴더의 파일": "此文件夹中的文件", @@ -2734,6 +2736,7 @@ "폴더는 보여도 그 안의 동영상 · 파일은 이 권한이 있어야 보입니다. 허용 화면에서 JJ_MKVMaker 를 켜고 돌아오세요.": "虽然能看到文件夹,但其中的视频和文件需要此权限。请在权限界面开启 JJ_MKVMaker 后返回。", "폴더를 만들 수 없습니다: {0}": "无法创建文件夹: {0}", "폴더를 읽는 중…": "正在读取文件夹…", + "폴더를 읽을 수 없습니다: {0}": "无法读取文件夹: {0}", "폴더를 자기 안으로 복사 · 이동할 수 없습니다": "不能将文件夹复制或移动到其自身内部", "폴더를 자기 안으로 복사할 수 없습니다": "不能将文件夹复制到其自身内部", "폴더를 자기 안으로 옮길 수 없습니다": "不能将文件夹移动到其自身内部", diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index e96ee68..2d55362 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -1765,6 +1765,28 @@ class _ExplorerPageState extends State { await openReader(context, c, ImageFilesSource(images, tempDir: temp, title: vBasename(dir)), start: start); } + /// X-plore 맞추기 (관리자 지시 10/20): 폴더의 그림 (지금 정렬 순서) 을 보기 화면으로 열고 [계속 보기] 를 켠 채 시작 + Future _slideshow(_Pane pane, String dir) async { + List list; + try { + list = pane.cache[dir] ?? _sorted(await listEntries(dir, showHidden: c.settings.explorerShowHidden), dir); + } catch (e) { + _snack(trf('폴더를 읽을 수 없습니다: {0}', [e])); + return; + } + final images = [ + for (final e in list) + if (!e.isDir && c.settings.imageExts.contains(extOf(e.path)) && (_showAppHidden || !_appHidden(e.path))) e.path, + ]; + if (images.isEmpty) { + _snack(tr('이 폴더에는 그림이 없습니다')); + return; + } + final temp = await c.services.storage.tempDirectory(); + if (!mounted) return; + await openReader(context, c, ImageFilesSource(images, tempDir: temp, title: vBasename(dir)), autoPlay: true); + } + /// 문서 → PDF (진행 창) → 보기 Future _viewDoc(String path) async { final temp = await c.services.storage.tempDirectory(); @@ -1999,6 +2021,8 @@ class _ExplorerPageState extends State { if (!e.isDir && !dav) ('hash', Icons.tag, tr('해시 (MD5 · SHA-256)')), if (e.isDir && dual) ('openOther', Icons.vertical_split_outlined, tr('다른 창에서 열기')), if (e.isDir) ('playFolder', Icons.play_circle_outline, tr('이 폴더의 동영상 재생')), + // X-plore 맞추기 (관리자 지시 10/20): 이 폴더의 그림을 [계속 보기] 로 + if (e.isDir && viewer) ('slideshow', Icons.slideshow_outlined, tr('사진 슬라이드쇼')), // X-plore 비교 g: 그림 · 동영상을 썸네일 격자로 if (e.isDir && !dav) ('grid', Icons.grid_view, tr('격자로 보기 (그림 · 동영상)')), if (video || audio) ('playInternal', Icons.play_circle_outline, tr('내장 플레이어로 재생')), @@ -2075,6 +2099,8 @@ class _ExplorerPageState extends State { setState(() => _active = _panes.indexOf(other)); case 'playFolder': await _playFolder(e.path); + case 'slideshow': + await _slideshow(pane, e.path); case 'playInternal': await _play([e.path], internal: true); case 'grid': diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index fa450e6..27750a7 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -15,9 +15,9 @@ import 'ai_upscale_ui.dart'; import 'theme.dart'; /// 그림 · 만화 · PDF 보기를 연다. [start] 처음 볼 장. 닫으면 [source] 를 정리한다. -Future openReader(BuildContext context, AppController c, ReaderSource source, {int start = 0}) async { +Future openReader(BuildContext context, AppController c, ReaderSource source, {int start = 0, bool autoPlay = false}) async { await Navigator.of(context).push(MaterialPageRoute( - builder: (_) => ReaderPage(c: c, source: source, start: start), + builder: (_) => ReaderPage(c: c, source: source, start: start, autoPlay: autoPlay), )); await source.dispose(); } @@ -45,7 +45,10 @@ class ReaderPage extends StatefulWidget { final AppController c; final ReaderSource source; final int start; - const ReaderPage({super.key, required this.c, required this.source, this.start = 0}); + + /// X-plore 맞추기 (관리자 지시 10/20): 폴더 [사진 슬라이드쇼] - [계속 보기] (117) 를 켠 채 연다 + final bool autoPlay; + const ReaderPage({super.key, required this.c, required this.source, this.start = 0, this.autoPlay = false}); /// 계속 보기 동안 화면 켜 두기 (시험에서 바꿈) static Future Function(bool on) keepScreenOn = (on) => on ? WakelockPlus.enable() : WakelockPlus.disable(); @@ -81,6 +84,7 @@ class _ReaderPageState extends State { _applySystemBars(); // 224: 앱으로 돌아올 때 그사이 밖에서 지운 그림을 목록에서 뺀다 _life = AppLifecycleListener(onResume: () => unawaited(dropMissingFiles())); + if (widget.autoPlay) WidgetsBinding.instance.addPostFrameCallback((_) => mounted ? _play(true) : null); } late final AppLifecycleListener _life; diff --git a/test/explorer_test.dart b/test/explorer_test.dart index c1ad763..1f49172 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -24,6 +24,8 @@ import 'package:jj_mkvmaker/ui/explorer_grid.dart'; import 'package:jj_mkvmaker/ui/text_viewer.dart'; import 'package:jj_mkvmaker/ui/storage_page.dart'; import 'package:jj_mkvmaker/ui/path_label.dart'; +import 'package:image/image.dart' as img; +import 'package:jj_mkvmaker/ui/reader_page.dart' show ReaderPage; import 'package:path/path.dart' as p; import 'support/fake_recycle_bin.dart'; import 'package:jj_mkvmaker/platform/windows/recycle_bin.dart' show recycleBinForTest; @@ -167,6 +169,31 @@ void main() { await tester.pump(const Duration(seconds: 2)); }); + testWidgets('X-plore 맞추기 (관리자 지시 10/20): 폴더 [사진 슬라이드쇼] - 그 폴더 그림을 [계속 보기] 로 열기 · 그림이 없으면 알림', (tester) async { + for (final n in ['b.png', 'a.png']) { + File(p.join(left, 'sub', n)).writeAsBytesSync(img.encodePng(img.Image(width: 20, height: 30))); + } + Directory(p.join(left, 'empty')).createSync(); + await open(tester); + await tester.longPress(find.text('sub')); + await calm(tester); + await act(tester, () => tester.tap(find.text('사진 슬라이드쇼'))); + await settle(tester, () => find.byType(ReaderPage).evaluate().isNotEmpty); + expect(find.text('1 / 2'), findsOneWidget); + expect(find.text('a.png'), findsOneWidget, reason: '지금 정렬 순서 (이름) 의 첫 그림부터'); + expect(find.byTooltip('계속 보기 멈추기'), findsOneWidget, reason: '[계속 보기] 를 켠 채 시작'); + await act(tester, () async => tester.state(find.byType(Navigator).first).pop()); + await calm(tester); + // 그림이 없는 폴더 + await tester.longPress(find.text('empty')); + await calm(tester); + await act(tester, () => tester.tap(find.text('사진 슬라이드쇼'))); + await settle(tester, () => find.text('이 폴더에는 그림이 없습니다').evaluate().isNotEmpty); + expect(find.byType(ReaderPage), findsNothing); + ScaffoldMessenger.of(tester.element(find.byType(ExplorerPage))).clearSnackBars(); + await calm(tester); + }); + testWidgets('X-plore P3: 길게 누르기 [해시] (MD5 · SHA-256) · 폴더 [폴더 속성] · 빠른 보기 [16진]', (tester) async { await open(tester); // 해시: doc.txt = "hello" From 99de4f4bc956a2461bd49c5a012d58298409dd28 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:27:52 +0900 Subject: [PATCH 12/24] =?UTF-8?q?WIP=20X-plore=20=EB=A7=9E=EC=B6=94?= =?UTF-8?q?=EA=B8=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20):=20=EA=B7=B8=EB=A6=BC=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EC=95=84=EB=9E=98=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=EC=A4=84=20?= =?UTF-8?q?-=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=A4=84,=20=EB=88=84?= =?UTF-8?q?=EB=A5=B4=EB=A9=B4=20=EA=B7=B8=20=EC=9E=A5=EC=9C=BC=EB=A1=9C,?= =?UTF-8?q?=20=EC=A7=80=EA=B8=88=20=EC=9E=A5=EC=9D=80=20=ED=85=8C=EB=91=90?= =?UTF-8?q?=EB=A6=AC=20=C2=B7=20=EC=9E=A5=EC=9D=B4=20=EB=B0=94=EB=80=8C?= =?UTF-8?q?=EB=A9=B4=20=EB=94=B0=EB=9D=BC=EA=B0=90=20=C2=B7=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=BC=20=EC=9B=90=EB=B3=B8=20(=ED=8F=B4=EB=8D=94=20=C2=B7?= =?UTF-8?q?=20ZIP)=20=EB=A7=8C=20=C2=B7=20=EC=B2=98=EC=9D=8C=20=EB=81=94?= =?UTF-8?q?=20(=EC=9C=84=20=EB=A7=89=EB=8C=80=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=C2=B7=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95=20>=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=EC=97=90=EC=84=9C=20=EC=BC=AC)=20=C2=B7=20?= =?UTF-8?q?=EC=8B=9C=ED=97=98=20(=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 --- assets/l10n/en.json | 4 ++ assets/l10n/ja.json | 4 ++ assets/l10n/zh-Hans.json | 4 ++ lib/app/settings.dart | 5 ++ lib/ui/component_settings.dart | 8 +++ lib/ui/reader_page.dart | 89 +++++++++++++++++++++++++++++++--- test/reader_test.dart | 26 ++++++++++ 7 files changed, 134 insertions(+), 6 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index ccfbbdd..0b77140 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -800,11 +800,13 @@ "그림 · 만화 보기, PDF 보기 · 페이지 편집, ZIP 목록 · 만화 보기": "Images · comic reading, PDF view · page editing, ZIP list · comic reading", "그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})": "Images and prompts leave this device ({0})", "그림 보기 대상": "Image viewer file types", + "그림 보기 아래 갤러리 줄": "Gallery strip in the image viewer", "그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식": "Can't read the image file - damaged or unsupported format", "그림으로 바꾸는 중": "Turning into an image", "그림을 만들지 못했습니다: {0}": "Couldn't make the image: {0}", "그림을 바꿔 만들기": "Make from this picture", "그림을 받을 수 없습니다 ({0})": "Couldn't get the image ({0})", + "그림을 볼 때 아래에 작은 그림 줄을 보입니다 (누르면 그 장으로)": "Shows a row of thumbnails at the bottom while viewing images (tap one to jump to it)", "그림을 열 수 없습니다": "Can't open the image", "그림을 올릴 수 없습니다 ({0})": "Couldn't upload the image ({0})", "그림을 읽을 수 없습니다": "Cannot read the image", @@ -1732,6 +1734,8 @@ "쓸 수 없는 이름": "Invalid name", "쓸 수 없는 이름입니다": "This name cannot be used", "아랍어": "Arabic", + "아래 갤러리 줄 보이기": "Show the gallery strip", + "아래 갤러리 줄 숨기기": "Hide the gallery strip", "아래 프로그램이 없어 일부 기능을 쓸 수 없습니다. 공식 배포처에서 내려받아 프로그램 폴더에 설치할까요?": "Some features are unavailable because the programs below are missing. Download them from the official sources and install them in the program folder?", "아래로": "Down", "아래에 줄 추가": "Add line below", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index c8877da..2808cb3 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -800,11 +800,13 @@ "그림 · 만화 보기, PDF 보기 · 페이지 편집, ZIP 목록 · 만화 보기": "画像・マンガ表示、PDF 表示・ページ編集、ZIP 一覧・マンガ表示", "그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})": "画像・プロンプトが端末の外に送られます ({0})", "그림 보기 대상": "画像表示の対象", + "그림 보기 아래 갤러리 줄": "画像ビューアー下のギャラリー列", "그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식": "画像ファイルを読み込めません - 破損しているか未対応の形式", "그림으로 바꾸는 중": "画像に変換中", "그림을 만들지 못했습니다: {0}": "画像を作成できませんでした: {0}", "그림을 바꿔 만들기": "画像を変えて作成", "그림을 받을 수 없습니다 ({0})": "画像を受け取れません ({0})", + "그림을 볼 때 아래에 작은 그림 줄을 보입니다 (누르면 그 장으로)": "画像を見るとき下にサムネイルの列を表示します (タップでその画像へ)", "그림을 열 수 없습니다": "画像を開けません", "그림을 올릴 수 없습니다 ({0})": "画像をアップロードできません ({0})", "그림을 읽을 수 없습니다": "画像を読み込めません", @@ -1732,6 +1734,8 @@ "쓸 수 없는 이름": "使えない名前", "쓸 수 없는 이름입니다": "使用できない名前です", "아랍어": "アラビア語", + "아래 갤러리 줄 보이기": "下のギャラリー列を表示", + "아래 갤러리 줄 숨기기": "下のギャラリー列を隠す", "아래 프로그램이 없어 일부 기능을 쓸 수 없습니다. 공식 배포처에서 내려받아 프로그램 폴더에 설치할까요?": "以下のプログラムがないため一部の機能が使えません。公式配布元からダウンロードしてプログラムフォルダーにインストールしますか?", "아래로": "下へ", "아래에 줄 추가": "下に行を追加", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index f1f3c36..a2f68c3 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -800,11 +800,13 @@ "그림 · 만화 보기, PDF 보기 · 페이지 편집, ZIP 목록 · 만화 보기": "图片 · 漫画阅读,PDF 查看 · 页面编辑,ZIP 列表 · 漫画阅读", "그림 · 프롬프트가 기기 밖으로 나갑니다 ({0})": "图片和提示词将离开本设备 ({0})", "그림 보기 대상": "图片查看的文件类型", + "그림 보기 아래 갤러리 줄": "图片查看器底部图库条", "그림 파일을 읽을 수 없습니다 - 깨졌거나 지원하지 않는 형식": "无法读取图片文件 - 已损坏或格式不受支持", "그림으로 바꾸는 중": "正在生成图像", "그림을 만들지 못했습니다: {0}": "无法生成图片: {0}", "그림을 바꿔 만들기": "基于图片生成", "그림을 받을 수 없습니다 ({0})": "无法获取图片 ({0})", + "그림을 볼 때 아래에 작은 그림 줄을 보입니다 (누르면 그 장으로)": "查看图片时在底部显示缩略图条 (点按可跳到该图)", "그림을 열 수 없습니다": "无法打开图片", "그림을 올릴 수 없습니다 ({0})": "无法上传图片 ({0})", "그림을 읽을 수 없습니다": "无法读取图片", @@ -1732,6 +1734,8 @@ "쓸 수 없는 이름": "无效名称", "쓸 수 없는 이름입니다": "不能使用此名称", "아랍어": "阿拉伯语", + "아래 갤러리 줄 보이기": "显示底部图库条", + "아래 갤러리 줄 숨기기": "隐藏底部图库条", "아래 프로그램이 없어 일부 기능을 쓸 수 없습니다. 공식 배포처에서 내려받아 프로그램 폴더에 설치할까요?": "由于缺少以下程序,部分功能无法使用。要从官方来源下载并安装到程序文件夹吗?", "아래로": "向下", "아래에 줄 추가": "在下方添加一行", diff --git a/lib/app/settings.dart b/lib/app/settings.dart index 5ccbb90..4cf8a82 100644 --- a/lib/app/settings.dart +++ b/lib/app/settings.dart @@ -555,6 +555,9 @@ class AppSettings { /// 117: 보기 화면 "계속 보기" 간격 (초, 1 ~ 60) int readerAutoSeconds = 3; + /// X-plore 맞추기 (관리자 지시 10/20): 그림 보기 화면 아래 갤러리 줄 (썸네일, 누르면 그 장으로). 처음 끔 + bool readerGallery = false; + /// 119: 한 장에 두 쪽이 붙은 그림을 반씩 나눠 보기 - 'auto' 가로가 세로보다 긴 그림만 · 'on' 늘 · 'off' 안 함 String readerSplit = 'auto'; @@ -867,6 +870,7 @@ class AppSettings { 'readerBrightness': readerBrightness, 'readerSystemBars': readerSystemBars, 'readerAutoSeconds': readerAutoSeconds, + 'readerGallery': readerGallery, 'readerSplit': readerSplit, 'startMenuShortcut': startMenuShortcut, 'syncStopToast': syncStopToast, @@ -1122,6 +1126,7 @@ class AppSettings { ..readerBrightness = ((j['readerBrightness'] as num?)?.toDouble() ?? 0).clamp(-0.7, 0.7) ..readerSystemBars = j['readerSystemBars'] != false ..readerAutoSeconds = ((j['readerAutoSeconds'] as num?)?.toInt() ?? 3).clamp(1, 60) + ..readerGallery = j['readerGallery'] as bool? ?? false ..readerSplit = const ['auto', 'on', 'off'].contains(j['readerSplit']) ? j['readerSplit'] as String : 'auto' ..startMenuShortcut = j['startMenuShortcut'] != false ..syncStopToast = j['syncStopToast'] != false diff --git a/lib/ui/component_settings.dart b/lib/ui/component_settings.dart index 98abc12..2833a2b 100644 --- a/lib/ui/component_settings.dart +++ b/lib/ui/component_settings.dart @@ -64,6 +64,14 @@ class ViewerSettings extends StatelessWidget { onChanged: (v) => c.updateSettings((x) => x.readerSplit = v!), ), ), + // X-plore 맞추기 (관리자 지시 10/20): 아래 갤러리 줄 (보기 화면 위 막대에서도 켜고 끔) + SwitchListTile( + secondary: const Icon(Icons.view_carousel_outlined), + value: s.readerGallery, + onChanged: (v) => c.updateSettings((x) => x.readerGallery = v), + title: Text(tr('그림 보기 아래 갤러리 줄')), + subtitle: Text(tr('그림을 볼 때 아래에 작은 그림 줄을 보입니다 (누르면 그 장으로)')), + ), // 117: 계속 보기 간격 SettingTile( leading: const Icon(Icons.slideshow_outlined), diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 27750a7..41f6f67 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -71,6 +71,12 @@ class _ReaderPageState extends State { /// PDF 는 페이지를 지우거나 넣으면 다시 그린다 int _version = 0; + /// X-plore 맞추기 (관리자 지시 10/20): 아래 갤러리 줄 - 그림 원본 (폴더 · ZIP) 에서만 + bool get _pictures => src is ImageFilesSource || src is ZipImagesSource; + final _thumbs = >{}; + final _gallery = ScrollController(); + static const _thumbExtent = 64.0; + /// [다시 시도] 를 누른 횟수 (장마다) - 그 장의 화면을 새로 만든다 (28 지적: 같은 키면 실패한 그림을 그대로 다시 썼다) final _retry = {}; @@ -344,6 +350,10 @@ class _ReaderPageState extends State { @override void dispose() { + for (final f in _thumbs.values) { + unawaited(f.then((x) => x.dispose(), onError: (_) {})); + } + _gallery.dispose(); _life.dispose(); _timer?.cancel(); if (_playing) unawaited(ReaderPage.keepScreenOn(false).catchError((_) {})); @@ -653,6 +663,10 @@ class _ReaderPageState extends State { one(_Act(const Icon(Icons.delete_outline), tr('이 페이지 지우기'), _deletePage)), one(_Act(const Icon(Icons.add_photo_alternate_outlined), tr('그림을 페이지로 넣기 (이 페이지 뒤)'), _insertImages)), ], + // X-plore 맞추기 (관리자 지시 10/20): 아래 갤러리 줄 켜고 끄기 (그림 원본만) + if (_pictures) + one(_Act(const Icon(Icons.view_carousel_outlined), s.readerGallery ? tr('아래 갤러리 줄 숨기기') : tr('아래 갤러리 줄 보이기'), + _toggleGallery, on: s.readerGallery)), // 119 · 120 one(_Act(const Icon(Icons.vertical_split_outlined), split, _cycleSplit, on: s.readerSplit != 'off')), one(_Act(const Icon(Icons.rotate_right), tr('90° 회전 (시계 방향)'), () => _rotate(1), on: _turns != 0)), @@ -810,13 +824,72 @@ class _ReaderPageState extends State { /// 쪽 번호: 통째면 "12", 반쪽이면 "12-1" · "12-2" String _label(ReaderSlot s) => s.part < 0 ? '${s.page + 1}' : '${s.page + 1}-${s.part + 1}'; + Future _toggleGallery() async { + await c.updateSettings((x) => x.readerGallery = !x.readerGallery); + if (!mounted) return; + setState(() {}); + WidgetsBinding.instance.addPostFrameCallback((_) => _scrollGallery(animate: false)); + } + + /// [page] 번째 장 (파일) 으로 (두 쪽 나눠 보고 있으면 그 장의 첫 쪽) + void _goPage(int page) { + final i = _v.indexWhere((e) => e.page == page); + if (i >= 0 && _pages.hasClients) _pages.jumpToPage(i); + } + + /// 갤러리 줄을 지금 장이 가운데 오게 + void _scrollGallery({bool animate = true}) { + if (!_gallery.hasClients) return; + final pos = _gallery.position; + final to = (_cur.page * _thumbExtent - pos.viewportDimension / 2 + _thumbExtent / 2).clamp(0.0, pos.maxScrollExtent); + animate ? _gallery.animateTo(to, duration: const Duration(milliseconds: 200), curve: Curves.easeOut) : _gallery.jumpTo(to); + } + + Future _thumb(int i) => _thumbs.putIfAbsent(i, () => src.load(i, maxWidth: 160)); + + Widget _galleryStrip() => SizedBox( + height: 76, + child: ListView.builder( + controller: _gallery, + scrollDirection: Axis.horizontal, + reverse: c.settings.readerRtl, + itemExtent: _thumbExtent, + itemCount: src.length, + itemBuilder: (_, i) => GestureDetector( + key: ValueKey('thumb$i'), + onTap: () => _goPage(i), + child: Container( + margin: const EdgeInsets.all(3), + decoration: BoxDecoration( + border: Border.all(color: i == _cur.page ? JjColors.accent : Colors.white24, width: i == _cur.page ? 2 : 1)), + child: FutureBuilder( + future: _thumb(i), + builder: (_, snap) { + final r = snap.data; + if (snap.hasError) return const Icon(Icons.broken_image_outlined, color: Colors.white38, size: 20); + if (r == null) return const SizedBox(); + return r.image != null + ? RawImage(image: r.image, fit: BoxFit.cover) + : Image( + image: r.provider!, + fit: BoxFit.cover, + errorBuilder: (_, _, _) => const Icon(Icons.broken_image_outlined, color: Colors.white38, size: 20)); + }, + ), + ), + ), + ), + ); + Widget _bottomBar() { final n = src.length; final count = _v.length; return Container( color: _barColor, padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom, left: 12, right: 12), - child: Row(children: [ + child: Column(mainAxisSize: MainAxisSize.min, children: [ + if (_pictures && c.settings.readerGallery && n > 0) _galleryStrip(), + Row(children: [ // 125: 반으로 나눈 쪽이 있으면 전체 수가 파일 수 (장) 임을 단위로 ("2-1 / 3장") Text( count != n ? trf('{0} / {1}장', [_label(_cur), n]) : '${n == 0 ? 0 : _label(_cur)} / $n', @@ -843,6 +916,7 @@ class _ReaderPageState extends State { textAlign: TextAlign.end, style: const TextStyle(color: Colors.white70, fontSize: 12)), ), + ]), ]), ); } @@ -883,11 +957,14 @@ class _ReaderPageState extends State { // 확대해 보는 동안은 밀어도 넘기지 않는다 (그림 옮기기) physics: _holdPage ? const NeverScrollableScrollPhysics() : null, itemCount: _v.length, - onPageChanged: (i) => setState(() { - _index = i; - _resetZoom(); - _trim(); - }), + onPageChanged: (i) { + setState(() { + _index = i; + _resetZoom(); + _trim(); + }); + _scrollGallery(); + }, itemBuilder: (_, i) => _page(i, box.biggest), ), ), diff --git a/test/reader_test.dart b/test/reader_test.dart index d79e976..ab6c2f3 100644 --- a/test/reader_test.dart +++ b/test/reader_test.dart @@ -189,6 +189,32 @@ void main() { expect(AppSettings.fromJson(c.settings.toJson()).readerSystemBars, isFalse); }); + testWidgets('X-plore 맞추기: 아래 갤러리 줄 - 처음은 끔, 켜면 썸네일 · 누르면 그 장으로, 위 막대에서 끄기', (tester) async { + tester.view.physicalSize = const Size(1400, 1200); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + expect(AppSettings().readerGallery, isFalse, reason: '처음 값은 지금 동작 그대로'); + expect(AppSettings.fromJson({'readerGallery': true}).readerGallery, isTrue); + final files = [ + for (final (i, n) in ['a.png', 'b.png', 'c.png'].indexed) (File(p.join(tmp.path, n))..writeAsBytesSync(_png(i * 80, 0, 0))).path, + ]; + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: ImageFilesSource(files, tempDir: tmp.path, title: 'imgs')))); + await tester.pump(); + expect(find.byKey(const ValueKey('thumb0')), findsNothing, reason: '처음은 끔'); + await tester.tap(find.byTooltip('아래 갤러리 줄 보이기')); + await tester.pumpAndSettle(); + expect(c.settings.readerGallery, isTrue); + expect(find.byKey(const ValueKey('thumb2')), findsOneWidget); + await tester.tap(find.byKey(const ValueKey('thumb2'))); + await tester.pumpAndSettle(); + expect(find.text('3 / 3'), findsOneWidget, reason: '누른 장으로'); + await tester.tap(find.byTooltip('아래 갤러리 줄 숨기기')); + await tester.pumpAndSettle(); + expect(find.byKey(const ValueKey('thumb0')), findsNothing); + await tester.pumpWidget(const SizedBox()); + }); + testWidgets('그림이 깨졌으면 화면 글 + [자세히], 파일을 고친 뒤 [다시 시도] 하면 열린다 (28 지적: 같은 키 · 같은 그림이라 Image 가 다시 읽지 않았다 - 장의 키를 올리고 그림 캐시도 비움)', (tester) async { tester.view.physicalSize = const Size(900, 1200); tester.view.devicePixelRatio = 1; From ed24463192a09a91ae9b42e03f8cfd28fe2ccd99 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:29:55 +0900 Subject: [PATCH 13/24] =?UTF-8?q?WIP=20X-plore=20=EB=A7=9E=EC=B6=94?= =?UTF-8?q?=EA=B8=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20):=20=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B3=BC=EB=A5=A8=20=ED=82=A4=EB=A1=9C=20=EB=84=98=EA=B8=B0?= =?UTF-8?q?=EA=B8=B0=20(=EC=95=84=EB=9E=98=20=3D=20=EB=8B=A4=EC=9D=8C=20?= =?UTF-8?q?=EC=9E=A5,=20=EC=9C=84=20=3D=20=EC=9D=B4=EC=A0=84=20=EC=9E=A5,?= =?UTF-8?q?=20Android)=20-=20=EC=B2=98=EC=9D=8C=20=EB=81=94=20(=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EC=84=A4=EC=A0=95=20>=20=EB=B3=B4=EA=B8=B0),=20?= =?UTF-8?q?=EC=BC=B0=EC=9D=84=20=EB=95=8C=EB=A7=8C=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=EA=B0=80=EB=A1=9C?= =?UTF-8?q?=EC=B1=84=EA=B3=A0=20(=EC=B2=98=EB=A6=AC=ED=95=9C=20=ED=82=A4?= =?UTF-8?q?=EB=8A=94=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=B3=BC=EB=A5=A8?= =?UTF-8?q?=EC=9D=84=20=EB=B0=94=EA=BE=B8=EC=A7=80=20=EC=95=8A=EC=9D=8C)?= =?UTF-8?q?=20=EB=8F=99=EC=98=81=EC=83=81=20=C2=B7=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=80=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20?= =?UTF-8?q?=C2=B7=20=EC=8B=9C=ED=97=98=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=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 --- assets/l10n/en.json | 2 ++ assets/l10n/ja.json | 2 ++ assets/l10n/zh-Hans.json | 2 ++ lib/app/settings.dart | 6 ++++++ lib/ui/component_settings.dart | 9 +++++++++ lib/ui/reader_page.dart | 19 ++++++++++++++++- test/reader_test.dart | 37 +++++++++++++++++++++++++++++++++- 7 files changed, 75 insertions(+), 2 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 0b77140..47aa09d 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -1411,6 +1411,7 @@ "보기": "View", "보기 {0}초 · Ctrl+휠 확대/축소": "View {0} s · Ctrl+wheel to zoom", "보기 화면에 [품질 올리기]: AI 그림 (그림 → 그림, 약하게) 으로 다듬은 뒤 해상도 올리기를 이어서. 새 파일 (이름_q) 로 저장하며 AI 그림 모델이 필요합니다": "Shows [Improve quality] in the viewer: refines with AI image (image → image, light) and then upscales. Saves a new file (name_q); needs the AI image model", + "보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)": "In the viewer, volume down = next, volume up = previous (videos and other screens keep normal volume keys)", "보기 화면의 [계속 보기] 가 다음 장으로 넘기는 간격": "How often [Slideshow] in the viewer turns to the next page", "보내지 못했습니다": "Could not send", "보낼 것 {0} / 있는 것 {1}": "sending {0} / existing {1}", @@ -1461,6 +1462,7 @@ "복사했습니다.": "copied.", "볼 그림이 없습니다.": "No images to show.", "볼 수 없는 영상입니다 (삭제 · 지역 제한).": "This video is unavailable (deleted · region restricted).", + "볼륨 키로 넘기기": "Turn pages with volume keys", "분석": "Analyze", "분석 실패: {0} - {1}": "Analysis failed: {0} - {1}", "분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "Re-analyzed {0} videos that had failed", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 2808cb3..48bec88 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -1411,6 +1411,7 @@ "보기": "表示", "보기 {0}초 · Ctrl+휠 확대/축소": "表示 {0}秒・Ctrl+ホイールで拡大/縮小", "보기 화면에 [품질 올리기]: AI 그림 (그림 → 그림, 약하게) 으로 다듬은 뒤 해상도 올리기를 이어서. 새 파일 (이름_q) 로 저장하며 AI 그림 모델이 필요합니다": "ビューアーに [画質を上げる]: AI 画像 (画像 → 画像、弱め) で整えてから解像度を上げます。新しいファイル (名前_q) に保存し、AI 画像モデルが必要です", + "보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)": "ビューアーで音量下 = 次、音量上 = 前 (動画・ほかの画面の音量はそのまま)", "보기 화면의 [계속 보기] 가 다음 장으로 넘기는 간격": "表示画面の [連続表示] が次のページへ進む間隔", "보내지 못했습니다": "送信できませんでした", "보낼 것 {0} / 있는 것 {1}": "送る方 {0} / 既存 {1}", @@ -1461,6 +1462,7 @@ "복사했습니다.": "コピーしました。", "볼 그림이 없습니다.": "表示する画像がありません。", "볼 수 없는 영상입니다 (삭제 · 지역 제한).": "視聴できない動画です(削除・地域制限)。", + "볼륨 키로 넘기기": "音量キーでページ送り", "분석": "分析", "분석 실패: {0} - {1}": "分析失敗: {0} - {1}", "분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "分析に失敗していた動画 {0} 件を再分析しました", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index a2f68c3..47c32f6 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -1411,6 +1411,7 @@ "보기": "查看", "보기 {0}초 · Ctrl+휠 확대/축소": "显示 {0} 秒 · Ctrl+滚轮缩放", "보기 화면에 [품질 올리기]: AI 그림 (그림 → 그림, 약하게) 으로 다듬은 뒤 해상도 올리기를 이어서. 새 파일 (이름_q) 로 저장하며 AI 그림 모델이 필요합니다": "在查看器中显示 [提升画质]: 先用 AI 图片 (图生图,轻度) 优化再提高分辨率。保存为新文件 (名称_q),需要 AI 图片模型", + "보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)": "在查看器中音量减 = 下一张, 音量加 = 上一张 (视频和其他界面的音量不变)", "보기 화면의 [계속 보기] 가 다음 장으로 넘기는 간격": "浏览画面中 [连续浏览] 翻到下一页的间隔", "보내지 못했습니다": "无法发送", "보낼 것 {0} / 있는 것 {1}": "要发送 {0} / 已有 {1}", @@ -1461,6 +1462,7 @@ "복사했습니다.": "已复制。", "볼 그림이 없습니다.": "没有可显示的图片。", "볼 수 없는 영상입니다 (삭제 · 지역 제한).": "该视频无法观看(已删除 · 地区限制)。", + "볼륨 키로 넘기기": "用音量键翻页", "분석": "分析", "분석 실패: {0} - {1}": "分析失败: {0} - {1}", "분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "已重新分析之前分析失败的 {0} 个视频", diff --git a/lib/app/settings.dart b/lib/app/settings.dart index 4cf8a82..88463fb 100644 --- a/lib/app/settings.dart +++ b/lib/app/settings.dart @@ -558,6 +558,10 @@ class AppSettings { /// X-plore 맞추기 (관리자 지시 10/20): 그림 보기 화면 아래 갤러리 줄 (썸네일, 누르면 그 장으로). 처음 끔 bool readerGallery = false; + /// X-plore 맞추기 (관리자 지시 10/20): 보기 화면에서 볼륨 키로 넘기기 (아래 = 다음 장, 위 = 이전 장, Android). 처음 끔. + /// 보기 화면에서만 가로챈다 (동영상 · 다른 화면의 볼륨은 그대로) + bool readerVolumeKeys = false; + /// 119: 한 장에 두 쪽이 붙은 그림을 반씩 나눠 보기 - 'auto' 가로가 세로보다 긴 그림만 · 'on' 늘 · 'off' 안 함 String readerSplit = 'auto'; @@ -871,6 +875,7 @@ class AppSettings { 'readerSystemBars': readerSystemBars, 'readerAutoSeconds': readerAutoSeconds, 'readerGallery': readerGallery, + 'readerVolumeKeys': readerVolumeKeys, 'readerSplit': readerSplit, 'startMenuShortcut': startMenuShortcut, 'syncStopToast': syncStopToast, @@ -1127,6 +1132,7 @@ class AppSettings { ..readerSystemBars = j['readerSystemBars'] != false ..readerAutoSeconds = ((j['readerAutoSeconds'] as num?)?.toInt() ?? 3).clamp(1, 60) ..readerGallery = j['readerGallery'] as bool? ?? false + ..readerVolumeKeys = j['readerVolumeKeys'] as bool? ?? false ..readerSplit = const ['auto', 'on', 'off'].contains(j['readerSplit']) ? j['readerSplit'] as String : 'auto' ..startMenuShortcut = j['startMenuShortcut'] != false ..syncStopToast = j['syncStopToast'] != false diff --git a/lib/ui/component_settings.dart b/lib/ui/component_settings.dart index 2833a2b..073fba4 100644 --- a/lib/ui/component_settings.dart +++ b/lib/ui/component_settings.dart @@ -72,6 +72,15 @@ class ViewerSettings extends StatelessWidget { title: Text(tr('그림 보기 아래 갤러리 줄')), subtitle: Text(tr('그림을 볼 때 아래에 작은 그림 줄을 보입니다 (누르면 그 장으로)')), ), + // X-plore 맞추기 (관리자 지시 10/20): 볼륨 키로 넘기기 (Android) + if (Platform.isAndroid) + SwitchListTile( + secondary: const Icon(Icons.volume_down_outlined), + value: s.readerVolumeKeys, + onChanged: (v) => c.updateSettings((x) => x.readerVolumeKeys = v), + title: Text(tr('볼륨 키로 넘기기')), + subtitle: Text(tr('보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)')), + ), // 117: 계속 보기 간격 SettingTile( leading: const Icon(Icons.slideshow_outlined), diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 41f6f67..16a4118 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io' show Platform; import 'dart:math' as math; import 'package:flutter/material.dart'; @@ -50,6 +51,9 @@ class ReaderPage extends StatefulWidget { final bool autoPlay; const ReaderPage({super.key, required this.c, required this.source, this.start = 0, this.autoPlay = false}); + /// 볼륨 키로 넘기기를 쓰는 기기인지 (Android - 시험에서 바꿈) + static bool Function() volumeKeysSupported = () => Platform.isAndroid; + /// 계속 보기 동안 화면 켜 두기 (시험에서 바꿈) static Future Function(bool on) keepScreenOn = (on) => on ? WakelockPlus.enable() : WakelockPlus.disable(); @@ -412,6 +416,15 @@ class _ReaderPageState extends State { _jumpLater(); } + /// X-plore 맞추기 (관리자 지시 10/20): 볼륨 키로 넘기기 (설정을 켰고 Android 일 때만 - 처리한 키는 시스템 볼륨을 바꾸지 않는다) + KeyEventResult _onKey(FocusNode _, KeyEvent e) { + if (!c.settings.readerVolumeKeys || !ReaderPage.volumeKeysSupported()) return KeyEventResult.ignored; + final k = e.logicalKey; + if (k != LogicalKeyboardKey.audioVolumeDown && k != LogicalKeyboardKey.audioVolumeUp) return KeyEventResult.ignored; + if (e is KeyDownEvent || e is KeyRepeatEvent) _go(k == LogicalKeyboardKey.audioVolumeDown ? 1 : -1); + return KeyEventResult.handled; + } + void _go(int delta) { final to = (_index + delta).clamp(0, math.max(0, _v.length - 1)).toInt(); if (to == _index) { @@ -933,7 +946,10 @@ class _ReaderPageState extends State { Navigator.of(context).pop(); } }, - child: Scaffold( + child: Focus( + autofocus: true, + onKeyEvent: _onKey, + child: Scaffold( backgroundColor: Colors.black, body: LayoutBuilder(builder: (context, box) { // 화면 너비 (기기 픽셀) 에 맞춰 그림을 줄여 읽는다 @@ -977,6 +993,7 @@ class _ReaderPageState extends State { ], ]); }), + ), ), ); } diff --git a/test/reader_test.dart b/test/reader_test.dart index ab6c2f3..c512e38 100644 --- a/test/reader_test.dart +++ b/test/reader_test.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'dart:typed_data'; import 'package:archive/archive_io.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:image/image.dart' as img; import 'package:jj_mkvmaker/app/app_controller.dart'; @@ -215,6 +215,41 @@ void main() { await tester.pumpWidget(const SizedBox()); }); + testWidgets('X-plore 맞추기: 볼륨 키로 넘기기 - 처음은 끔 (키를 가로채지 않음), 켜면 아래 = 다음 · 위 = 이전 (Android 만)', (tester) async { + tester.view.physicalSize = const Size(900, 1200); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final was = ReaderPage.volumeKeysSupported; + addTearDown(() => ReaderPage.volumeKeysSupported = was); + ReaderPage.volumeKeysSupported = () => true; + expect(AppSettings().readerVolumeKeys, isFalse); + final files = [ + for (final (i, n) in ['a.png', 'b.png', 'c.png'].indexed) (File(p.join(tmp.path, n))..writeAsBytesSync(_png(i * 80, 0, 0))).path, + ]; + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: ImageFilesSource(files, tempDir: tmp.path, title: 'imgs')))); + await tester.pump(); + // 끔: 볼륨 키를 가로채지 않는다 (시스템 볼륨이 바뀜) + expect(await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown), isFalse); + await tester.pumpAndSettle(); + expect(find.text('1 / 3'), findsOneWidget); + // 켬 + c.settings.readerVolumeKeys = true; + expect(await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown), isTrue, reason: '처리함 - 시스템 볼륨은 바뀌지 않음'); + await tester.pumpAndSettle(); + expect(find.text('2 / 3'), findsOneWidget); + await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown); + await tester.pumpAndSettle(); + expect(find.text('3 / 3'), findsOneWidget); + await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeUp); + await tester.pumpAndSettle(); + expect(find.text('2 / 3'), findsOneWidget); + // Android 가 아니면 쓰지 않음 + ReaderPage.volumeKeysSupported = () => false; + expect(await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown), isFalse); + await tester.pumpWidget(const SizedBox()); + }); + testWidgets('그림이 깨졌으면 화면 글 + [자세히], 파일을 고친 뒤 [다시 시도] 하면 열린다 (28 지적: 같은 키 · 같은 그림이라 Image 가 다시 읽지 않았다 - 장의 키를 올리고 그림 캐시도 비움)', (tester) async { tester.view.physicalSize = const Size(900, 1200); tester.view.devicePixelRatio = 1; From 1f0c094656784de3e6081c55eb21491b8d499ee4 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:35:34 +0900 Subject: [PATCH 14/24] =?UTF-8?q?WIP=20X-plore=20=EB=A7=9E=EC=B6=94?= =?UTF-8?q?=EA=B8=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20):=20=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=A0=95=EB=B3=B4=20(Exif)=20-=20?= =?UTF-8?q?=EC=9C=84=20=EB=A7=89=EB=8C=80=20[=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20(Exif)]=20(=ED=8F=B4=EB=8D=94=EC=9D=98=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=BC=EB=A7=8C):=20=ED=8C=8C=EC=9D=BC=20=C2=B7=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20(=ED=94=BD=EC=85=80=20=C2=B7=20=EB=B0=94?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8)=20=C2=B7=20=EC=B0=8D=EC=9D=80=20=EB=82=A0?= =?UTF-8?q?=20=C2=B7=20=EA=B8=B0=EA=B8=B0=20=C2=B7=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?-=20=EC=9C=84=EC=B9=98=EB=8A=94=20[=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0]=20=EB=A5=BC=20=EB=88=8C=EB=9F=AC=EC=95=BC?= =?UTF-8?q?=20=EB=B3=B4=EC=9D=B4=EA=B3=A0=20=EC=A7=80=EB=8F=84=20=C2=B7=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EB=84=B7=EC=9C=BC=EB=A1=9C=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EC=A7=80=20=EC=95=8A=EC=9D=8C=20(=EB=A7=81=ED=81=AC?= =?UTF-8?q?=20=EC=97=86=EC=9D=8C)=20=C2=B7=20Exif=20=EC=97=86=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EC=95=88=EB=82=B4=20=C2=B7=20image=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20(MIT,=20=EC=9D=B4=EB=AF=B8=20=EC=94=80)=20?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=BD=EC=96=B4=20=EC=83=88=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=20=EC=97=86=EC=9D=8C=20=C2=B7=20=EC=8B=9C=ED=97=98=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 --- assets/l10n/en.json | 7 ++++ assets/l10n/ja.json | 7 ++++ assets/l10n/zh-Hans.json | 7 ++++ lib/core/photo_info.dart | 70 +++++++++++++++++++++++++++++++ lib/ui/reader_page.dart | 82 +++++++++++++++++++++++++++++++++++- test/photo_info_test.dart | 87 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 lib/core/photo_info.dart create mode 100644 test/photo_info_test.dart diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 47aa09d..bf5fc7a 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -499,6 +499,7 @@ "{0} · 그림 · 동영상 {1}개": "{0} · {1} pictures / videos", "{0} · 링크 {1}개 제외": "{0} · {1} links excluded", "{0} · 파일 {1}개": "{0} · {1} files", + "{0} × {1} 픽셀": "{0} × {1} px", "{0} → {1} 번역 {2}%": "{0} → {1} translation {2}%", "{0} 는 이미 끝났습니다": "{0} has already finished", "{0} 라이선스": "{0} license", @@ -1526,6 +1527,7 @@ "사용할 수 있는 자막 사이트가 없습니다.": "No subtitle site is available.", "사진": "Photos", "사진 슬라이드쇼": "Photo slideshow", + "사진 정보 (Exif)": "Photo info (Exif)", "사진에는 '사진' 모델이 더 낫지만, 이 기기 (그래픽카드 없음) 에서는 한 장에 약 20분 걸립니다.": "The 'Photos' model is better for photos, but on this device (no graphics card) it takes about 20 minutes per image.", "사진용": "Photos", "삭제": "Delete", @@ -2022,6 +2024,9 @@ "위아래 ⇅": "Rows ⇅", "위쪽 이동 버튼과 좌우로 밀기의 순서입니다. ▲ ▼ 로 바꿉니다.": "Order of the top navigation buttons and of swiping. Change it with ▲ ▼.", "위쪽 이동 버튼과 좌우로 밀기의 순서입니다. 줄을 끌어 바꿉니다.": "Order of the top navigation buttons and of swiping. Drag a row to change it.", + "위치": "Location", + "위치 보기": "Show location", + "위치는 이 기기에서만 보이며 지도 · 인터넷으로 보내지 않습니다": "The location is shown only on this device and is never sent to a map or the internet", "유형": "Type", "음성": "Audio", "음성 {0}개": "{0} audio", @@ -2041,6 +2046,7 @@ "이 WebDAV 서버는 로그인이 필요합니다": "This WebDAV server requires sign-in", "이 {0}의 설정 파일에 저장됩니다": "Saved in this {0}'s settings file", "이 {0}의 안전 저장소에 저장됩니다 (설정 파일에는 쓰지 않음)": "Saved in this {0}'s secure storage (not in the settings file)", + "이 그림에는 Exif 정보가 없습니다 (JPEG 사진만 읽음)": "This image has no Exif info (only JPEG photos are read)", "이 그림을 바탕으로 (그림 → 그림)": "Use this as the base (image → image)", "이 기기 (SD1.5 + LCM)": "This device (SD1.5 + LCM)", "이 기기 ({0})": "This device ({0})", @@ -2513,6 +2519,7 @@ "집 NAS 처럼 자체 서명 인증서면 [서버 설정 고치기] 에서 \"인증서 확인 안 함\" 을 켜세요.": "If it uses a self-signed certificate (like a home NAS), turn on \"Don't verify certificate\" in [Edit server settings].", "짧을수록 빨리 맞추지만 배터리를 더 씁니다.": "Shorter means faster syncing but more battery use.", "쪽 범위가 맞지 않습니다": "The page range is invalid", + "찍은 날": "Taken", "차례 기다림": "Waiting in queue", "창": "Panes", "창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)": "Icon of the window, taskbar, tray and the top-left button in the app (the program file and desktop shortcut icons stay the same)", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 48bec88..32b4107 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -499,6 +499,7 @@ "{0} · 그림 · 동영상 {1}개": "{0} · 画像・動画 {1} 個", "{0} · 링크 {1}개 제외": "{0} · リンク {1} 件を除く", "{0} · 파일 {1}개": "{0} · ファイル {1} 件", + "{0} × {1} 픽셀": "{0} × {1} ピクセル", "{0} → {1} 번역 {2}%": "{0} → {1} 翻訳 {2}%", "{0} 는 이미 끝났습니다": "{0} はすでに終わりました", "{0} 라이선스": "{0} ライセンス", @@ -1526,6 +1527,7 @@ "사용할 수 있는 자막 사이트가 없습니다.": "使用できる字幕サイトがありません。", "사진": "写真", "사진 슬라이드쇼": "写真スライドショー", + "사진 정보 (Exif)": "写真情報 (Exif)", "사진에는 '사진' 모델이 더 낫지만, 이 기기 (그래픽카드 없음) 에서는 한 장에 약 20분 걸립니다.": "写真には「写真」モデルが適していますが、この端末 (グラフィックカードなし) では 1 枚に約 20 分かかります。", "사진용": "写真用", "삭제": "削除", @@ -2022,6 +2024,9 @@ "위아래 ⇅": "上下 ⇅", "위쪽 이동 버튼과 좌우로 밀기의 순서입니다. ▲ ▼ 로 바꿉니다.": "上部の移動ボタンと左右スワイプの順番です。▲ ▼ で変更します。", "위쪽 이동 버튼과 좌우로 밀기의 순서입니다. 줄을 끌어 바꿉니다.": "上部の移動ボタンと左右スワイプの順番です。行をドラッグして変更します。", + "위치": "位置", + "위치 보기": "位置を表示", + "위치는 이 기기에서만 보이며 지도 · 인터넷으로 보내지 않습니다": "位置はこの端末でのみ表示され、地図やインターネットには送信しません", "유형": "種類", "음성": "音声", "음성 {0}개": "音声 {0}個", @@ -2041,6 +2046,7 @@ "이 WebDAV 서버는 로그인이 필요합니다": "この WebDAV サーバーはログインが必要です", "이 {0}의 설정 파일에 저장됩니다": "この{0}の設定ファイルに保存されます", "이 {0}의 안전 저장소에 저장됩니다 (설정 파일에는 쓰지 않음)": "この {0} の安全な保存領域に保存されます (設定ファイルには書きません)", + "이 그림에는 Exif 정보가 없습니다 (JPEG 사진만 읽음)": "この画像には Exif 情報がありません (JPEG 写真のみ読み込み)", "이 그림을 바탕으로 (그림 → 그림)": "この画像を元に (画像 → 画像)", "이 기기 (SD1.5 + LCM)": "この端末 (SD1.5 + LCM)", "이 기기 ({0})": "この端末 ({0})", @@ -2513,6 +2519,7 @@ "집 NAS 처럼 자체 서명 인증서면 [서버 설정 고치기] 에서 \"인증서 확인 안 함\" 을 켜세요.": "自宅 NAS のように自己署名証明書なら、[サーバー設定を修正] で「証明書を検証しない」をオンにしてください。", "짧을수록 빨리 맞추지만 배터리를 더 씁니다.": "短いほど早く同期しますが、バッテリーをより使います。", "쪽 범위가 맞지 않습니다": "ページ範囲が正しくありません", + "찍은 날": "撮影日", "차례 기다림": "順番待ち", "창": "ペイン", "창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)": "ウィンドウ・タスクバー・トレイとアプリ内左上のボタンのアイコンです(実行ファイル・デスクトップのショートカットのアイコンはそのまま)", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 47c32f6..159cd9d 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -499,6 +499,7 @@ "{0} · 그림 · 동영상 {1}개": "{0} · {1} 个图片 / 视频", "{0} · 링크 {1}개 제외": "{0} · 已排除 {1} 个链接", "{0} · 파일 {1}개": "{0} · {1} 个文件", + "{0} × {1} 픽셀": "{0} × {1} 像素", "{0} → {1} 번역 {2}%": "{0} → {1} 翻译 {2}%", "{0} 는 이미 끝났습니다": "{0} 已经完成", "{0} 라이선스": "{0} 许可证", @@ -1526,6 +1527,7 @@ "사용할 수 있는 자막 사이트가 없습니다.": "没有可用的字幕网站。", "사진": "照片", "사진 슬라이드쇼": "照片幻灯片", + "사진 정보 (Exif)": "照片信息 (Exif)", "사진에는 '사진' 모델이 더 낫지만, 이 기기 (그래픽카드 없음) 에서는 한 장에 약 20분 걸립니다.": "照片更适合使用“照片”模型,但在本设备 (无显卡) 上每张约需 20 分钟。", "사진용": "照片", "삭제": "删除", @@ -2022,6 +2024,9 @@ "위아래 ⇅": "上下 ⇅", "위쪽 이동 버튼과 좌우로 밀기의 순서입니다. ▲ ▼ 로 바꿉니다.": "顶部导航按钮和左右滑动的顺序。用 ▲ ▼ 更改。", "위쪽 이동 버튼과 좌우로 밀기의 순서입니다. 줄을 끌어 바꿉니다.": "顶部导航按钮和左右滑动的顺序。拖动行即可更改。", + "위치": "位置", + "위치 보기": "显示位置", + "위치는 이 기기에서만 보이며 지도 · 인터넷으로 보내지 않습니다": "位置仅在本设备上显示,不会发送到地图或互联网", "유형": "类型", "음성": "音频", "음성 {0}개": "音频 {0} 个", @@ -2041,6 +2046,7 @@ "이 WebDAV 서버는 로그인이 필요합니다": "此 WebDAV 服务器需要登录", "이 {0}의 설정 파일에 저장됩니다": "保存在此{0}的设置文件中", "이 {0}의 안전 저장소에 저장됩니다 (설정 파일에는 쓰지 않음)": "保存在此 {0} 的安全存储中 (不写入设置文件)", + "이 그림에는 Exif 정보가 없습니다 (JPEG 사진만 읽음)": "此图片没有 Exif 信息 (仅读取 JPEG 照片)", "이 그림을 바탕으로 (그림 → 그림)": "以此图片为基础 (图片 → 图片)", "이 기기 (SD1.5 + LCM)": "本设备 (SD1.5 + LCM)", "이 기기 ({0})": "本设备 ({0})", @@ -2513,6 +2519,7 @@ "집 NAS 처럼 자체 서명 인증서면 [서버 설정 고치기] 에서 \"인증서 확인 안 함\" 을 켜세요.": "如果是自签名证书 (如家用 NAS),请在 [修改服务器设置] 中开启“不验证证书”。", "짧을수록 빨리 맞추지만 배터리를 더 씁니다.": "间隔越短同步越快,但更耗电。", "쪽 범위가 맞지 않습니다": "页面范围无效", + "찍은 날": "拍摄日期", "차례 기다림": "排队等待", "창": "窗格", "창 · 작업 표시줄 · 트레이와 앱 안 왼쪽 위 버튼의 아이콘입니다 (실행 파일 · 바탕 화면 바로 가기 아이콘은 그대로)": "窗口、任务栏、托盘以及应用内左上角按钮的图标(程序文件和桌面快捷方式的图标不变)", diff --git a/lib/core/photo_info.dart b/lib/core/photo_info.dart new file mode 100644 index 0000000..e7b29b2 --- /dev/null +++ b/lib/core/photo_info.dart @@ -0,0 +1,70 @@ +import 'dart:typed_data'; + +import 'package:image/image.dart' as img; + +/// X-plore 맞추기 (관리자 지시 10/20): 보기 화면의 사진 정보 (Exif) - 찍은 날 · 기기 · 크기 · 위치. +/// image 패키지 (MIT - 앱이 이미 쓰는 것, 라이선스 화면에 있음) 로 읽는다. 새 의존 없음. 위치는 보기 화면이 누를 때만 보이고, +/// 지도 · 인터넷으로 보내지 않는다 (이 파일은 숫자만 읽는다). +class PhotoInfo { + /// 그림 크기 (픽셀, 모르면 null) + final int? width, height; + + /// 찍은 날 ("2024-05-01 12:30:00", Exif 의 DateTimeOriginal · 없으면 DateTime) + final String? taken; + + /// 기기 ("Canon EOS 5D" - Make · Model) + final String? device; + + /// 위치 (도, 남 · 서는 음수) + final double? latitude, longitude; + + const PhotoInfo({this.width, this.height, this.taken, this.device, this.latitude, this.longitude}); + + bool get hasLocation => latitude != null && longitude != null; + + /// Exif 에서 읽은 것이 있는지 (크기는 Exif 가 없어도 읽음) + bool get hasExif => taken != null || device != null || hasLocation; + + /// 위치 글 ("37.500000, 127.000000") + String get locationText => hasLocation ? '${latitude!.toStringAsFixed(6)}, ${longitude!.toStringAsFixed(6)}' : ''; +} + +/// 그림 파일의 바이트에서 사진 정보를 읽는다 (Exif 는 JPEG 만 - 다른 형식은 크기만). 읽지 못하는 것은 null +PhotoInfo readPhotoInfo(Uint8List bytes) { + int? w, h; + try { + final info = img.findDecoderForData(bytes)?.startDecode(bytes); + if (info != null && info.width > 0 && info.height > 0) { + w = info.width; + h = info.height; + } + } catch (_) {} + String? taken, device; + double? lat, lon; + try { + final e = img.decodeJpgExif(bytes); + if (e != null) { + String? s(img.IfdValue? v) { + final t = v?.toString().replaceAll('\u0000', '').trim(); + return t == null || t.isEmpty ? null : t; + } + + final when = s(e.exifIfd['DateTimeOriginal']) ?? s(e.imageIfd['DateTime']); + // "2024:05:01 12:30:00" → "2024-05-01 12:30:00" + taken = when?.replaceFirstMapped(RegExp(r'^(\d{4}):(\d{2}):(\d{2})'), (m) => '${m[1]}-${m[2]}-${m[3]}'); + final make = s(e.imageIfd['Make']), model = s(e.imageIfd['Model']); + device = model == null ? make : (make == null || model.toLowerCase().startsWith(make.toLowerCase()) ? model : '$make $model'); + // GPS: 1 위도 N/S · 2 위도 (도 · 분 · 초) · 3 경도 E/W · 4 경도 + lat = _coord(e.gpsIfd[2], s(e.gpsIfd[1])); + lon = _coord(e.gpsIfd[4], s(e.gpsIfd[3])); + } + } catch (_) {} + return PhotoInfo(width: w, height: h, taken: taken, device: device, latitude: lat, longitude: lon); +} + +double? _coord(img.IfdValue? v, String? ref) { + if (v == null || v.length < 3) return null; + final d = v.toDouble(0) + v.toDouble(1) / 60 + v.toDouble(2) / 3600; + if (d.isNaN || d.isInfinite) return null; + return ref == 'S' || ref == 'W' ? -d : d; +} diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 16a4118..7d0d24e 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:io' show Platform; +import 'dart:io' show File, Platform; import 'dart:math' as math; import 'package:flutter/material.dart'; @@ -9,6 +9,8 @@ import 'package:wakelock_plus/wakelock_plus.dart'; import '../app/app_controller.dart'; import '../core/reader_sources.dart'; import '../core/hwp_source.dart' show HwpPdfException, HwpSource; +import '../core/file_ops.dart' show formatSize; +import '../core/photo_info.dart'; import '../core/vfs.dart'; import '../l10n/tr.dart'; import 'ai_image_page.dart'; @@ -676,6 +678,8 @@ class _ReaderPageState extends State { one(_Act(const Icon(Icons.delete_outline), tr('이 페이지 지우기'), _deletePage)), one(_Act(const Icon(Icons.add_photo_alternate_outlined), tr('그림을 페이지로 넣기 (이 페이지 뒤)'), _insertImages)), ], + // X-plore 맞추기 (관리자 지시 10/20): 사진 정보 (Exif) - 폴더의 그림만 + if (src is ImageFilesSource) one(_Act(const Icon(Icons.info_outline), tr('사진 정보 (Exif)'), _showPhotoInfo)), // X-plore 맞추기 (관리자 지시 10/20): 아래 갤러리 줄 켜고 끄기 (그림 원본만) if (_pictures) one(_Act(const Icon(Icons.view_carousel_outlined), s.readerGallery ? tr('아래 갤러리 줄 숨기기') : tr('아래 갤러리 줄 보이기'), @@ -837,6 +841,82 @@ class _ReaderPageState extends State { /// 쪽 번호: 통째면 "12", 반쪽이면 "12-1" · "12-2" String _label(ReaderSlot s) => s.part < 0 ? '${s.page + 1}' : '${s.page + 1}-${s.part + 1}'; + /// X-plore 맞추기 (관리자 지시 10/20): 지금 그림의 사진 정보 (찍은 날 · 기기 · 크기 · 위치). 위치는 [위치 보기] 를 누를 때만 + /// 보이고, 지도 · 인터넷으로 보내지 않는다 (링크 없음) + Future _showPhotoInfo() async { + final s = src; + if (s is! ImageFilesSource || _v.isEmpty) return; + final path = s.paths[_cur.page]; + PhotoInfo? info; + int? size; + Object? error; + try { + final local = await vLocalCopy(path, s.tempDir); + final f = File(local); + size = await f.length(); + final bytes = await f.readAsBytes(); + info = readPhotoInfo(bytes); + } catch (e) { + error = e; + } + if (!mounted) return; + var showLocation = false; + await showDialog( + context: context, + builder: (ctx) => StatefulBuilder( + builder: (ctx, set) { + final i = info; + Widget row(String k, Widget v) => Padding( + padding: const EdgeInsets.symmetric(vertical: 3), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox(width: 90, child: Text(k, style: const TextStyle(color: JjColors.textDim))), + Expanded(child: v), + ]), + ); + return AlertDialog( + scrollable: true, + title: Text(tr('사진 정보 (Exif)')), + content: SizedBox( + width: 420, + child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + row(tr('파일'), SelectableText(vBasename(path))), + if (i == null) + Text(trf('읽을 수 없습니다: {0}', [error])) + else ...[ + row(tr('크기'), Text([ + if (i.width != null) trf('{0} × {1} 픽셀', [i.width!, i.height!]), + if (size != null) formatSize(size), + ].join(' · '))), + row(tr('찍은 날'), Text(i.taken ?? '-')), + row(tr('기기'), Text(i.device ?? '-')), + row( + tr('위치'), + !i.hasLocation + ? const Text('-') + : showLocation + ? SelectableText(i.locationText) + : Align( + alignment: Alignment.centerLeft, + child: TextButton(onPressed: () => set(() => showLocation = true), child: Text(tr('위치 보기'))))), + if (i.hasLocation) + Text(tr('위치는 이 기기에서만 보이며 지도 · 인터넷으로 보내지 않습니다'), + style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + if (!i.hasExif) + Padding( + padding: const EdgeInsets.only(top: 6), + child: Text(tr('이 그림에는 Exif 정보가 없습니다 (JPEG 사진만 읽음)'), + style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + ), + ], + ]), + ), + actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(tr('닫기')))], + ); + }, + ), + ); + } + Future _toggleGallery() async { await c.updateSettings((x) => x.readerGallery = !x.readerGallery); if (!mounted) return; diff --git a/test/photo_info_test.dart b/test/photo_info_test.dart new file mode 100644 index 0000000..822c96f --- /dev/null +++ b/test/photo_info_test.dart @@ -0,0 +1,87 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:image/image.dart' as img; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/core/photo_info.dart'; +import 'package:jj_mkvmaker/core/reader_sources.dart'; +import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; +import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; +import 'package:jj_mkvmaker/services/platform_services.dart'; +import 'package:jj_mkvmaker/ui/reader_page.dart'; +import 'package:path/path.dart' as p; + +/// 도 · 분 · 초 (image 패키지가 Rational 을 내보내지 않아 IfdValueRational 하나씩 만들어 잇는다) +img.IfdValueRational _dms(int d, int m, int s) => + img.IfdValueRational(d, 1)..value.addAll([...img.IfdValueRational(m, 1).value, ...img.IfdValueRational(s, 1).value]); + +/// Exif 가 든 JPEG (찍은 날 · 기기 · 위치) +Uint8List _jpeg({bool gps = true, bool south = false}) { + final im = img.Image(width: 30, height: 20); + im.exif.imageIfd['Make'] = 'Canon'; + im.exif.imageIfd['Model'] = 'Canon EOS 5D'; + im.exif.exifIfd['DateTimeOriginal'] = '2024:05:01 12:30:00'; + if (gps) { + im.exif.gpsIfd[1] = img.IfdValueAscii(south ? 'S' : 'N'); + im.exif.gpsIfd[2] = _dms(37, 30, 0); + im.exif.gpsIfd[3] = img.IfdValueAscii('E'); + im.exif.gpsIfd[4] = _dms(127, 0, 36); + } + return Uint8List.fromList(img.encodeJpg(im)); +} + +/// X-plore 맞추기 (관리자 지시 10/20): 보기 화면 사진 정보 (Exif) - 찍은 날 · 기기 · 크기 · 위치 (누를 때만, 보내지 않음) +void main() { + test('Exif 읽기: 찍은 날 (2024-05-01) · 기기 (Make 가 Model 앞에 있으면 한 번만) · 크기 · 위치 (도 · 분 · 초 → 도, 남은 음수)', () { + final i = readPhotoInfo(_jpeg()); + expect(i.width, 30); + expect(i.height, 20); + expect(i.taken, '2024-05-01 12:30:00'); + expect(i.device, 'Canon EOS 5D'); + expect(i.latitude, closeTo(37.5, 1e-6)); + expect(i.longitude, closeTo(127.01, 1e-6)); + expect(i.locationText, '37.500000, 127.010000'); + expect(i.hasExif, isTrue); + expect(readPhotoInfo(_jpeg(south: true)).latitude, closeTo(-37.5, 1e-6)); + expect(readPhotoInfo(_jpeg(gps: false)).hasLocation, isFalse); + }); + + test('Exif 가 없는 그림 (PNG) 은 크기만, 깨진 바이트는 아무것도 없이 (예외 없음)', () { + final png = readPhotoInfo(Uint8List.fromList(img.encodePng(img.Image(width: 7, height: 9)))); + expect((png.width, png.height), (7, 9)); + expect(png.hasExif, isFalse); + final bad = readPhotoInfo(Uint8List.fromList([1, 2, 3])); + expect(bad.width, isNull); + expect(bad.hasExif, isFalse); + }); + + testWidgets('보기 화면 [사진 정보 (Exif)]: 찍은 날 · 기기 · 크기, 위치는 [위치 보기] 를 눌러야 보임 (링크 없음)', (tester) async { + tester.view.physicalSize = const Size(1400, 1200); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final tmp = Directory.systemTemp.createTempSync('jj_photoinfo_'); + addTearDown(() => tmp.deleteSync(recursive: true)); + final f = File(p.join(tmp.path, 'trip.jpg'))..writeAsBytesSync(_jpeg()); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: ImageFilesSource([f.path], tempDir: tmp.path, title: 'x')))); + await tester.pump(); + await tester.tap(find.byTooltip('사진 정보 (Exif)')); + for (var k = 0; k < 10; k++) { + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 30))); + await tester.pump(); + } + expect(find.text('2024-05-01 12:30:00'), findsOneWidget); + expect(find.text('Canon EOS 5D'), findsOneWidget); + expect(find.textContaining('30 × 20 픽셀'), findsOneWidget); + expect(find.textContaining('37.5'), findsNothing, reason: '위치는 누를 때만'); + await tester.tap(find.text('위치 보기')); + await tester.pump(); + expect(find.text('37.500000, 127.010000'), findsOneWidget); + expect(find.textContaining('http'), findsNothing, reason: '지도 · 인터넷 링크 없음'); + await tester.tap(find.text('닫기')); + await tester.pumpAndSettle(); + await tester.pumpWidget(const SizedBox()); + }); +} From fc0f6316f9c1146ac28d1b4358b4675dc187a90a Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:43:51 +0900 Subject: [PATCH 15/24] =?UTF-8?q?WIP=20X-plore=20=EB=A7=9E=EC=B6=94?= =?UTF-8?q?=EA=B8=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20):=20=EC=88=A8=EA=B8=B0=EA=B8=B0=20-=20[=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=97=90=EC=84=9C=20=EC=88=A8=EA=B8=B0=EA=B8=B0]=20?= =?UTF-8?q?=EC=A0=84=EC=97=90=20=EB=AC=B4=EC=97=87=EC=9D=B4=20=EB=B0=94?= =?UTF-8?q?=EB=80=8C=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8=20=EC=B0=BD=20?= =?UTF-8?q?(=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=EB=A7=8C=20=EB=B9=A0?= =?UTF-8?q?=EC=A7=80=EA=B3=A0=20=ED=8C=8C=EC=9D=BC=20=C2=B7=20=EB=8B=A4?= =?UTF-8?q?=EB=A5=B8=20=EC=95=B1=EC=9D=80=20=EA=B7=B8=EB=8C=80=EB=A1=9C,?= =?UTF-8?q?=20=EB=8B=A4=EC=8B=9C=20=EB=B3=B4=EB=8A=94=20=EA=B3=B3)=20?= =?UTF-8?q?=C2=B7=20[=EB=8B=A4=EC=9D=8C=EB=B6=80=ED=84=B0=20=EB=AC=BB?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EA=B8=B0]=20(=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20>=20=ED=8C=8C=EC=9D=BC=20=ED=83=90?= =?UTF-8?q?=EC=83=89=EA=B8=B0=20"=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=88=A8=EA=B8=B0=EA=B8=B0=20=EC=A0=84=EC=97=90=20=EB=AC=BB?= =?UTF-8?q?=EA=B8=B0",=20=EC=B2=98=EC=9D=8C=20=EC=BC=AC)=20=C2=B7=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EA=B8=B8=EA=B2=8C=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0=20[=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=C2=B7=20=EC=9D=8C?= =?UTF-8?q?=EC=95=85=20=EC=95=B1=EC=97=90=EC=84=9C=20=EC=88=A8=EA=B8=B0?= =?UTF-8?q?=EA=B8=B0=20(.nomedia)]=20/=20[=EB=8B=A4=EC=8B=9C=20=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EA=B8=B0=20(.nomedia=20=EC=A7=80=EC=9A=B0=EA=B8=B0)]?= =?UTF-8?q?=20(Android,=20=ED=99=95=EC=9D=B8=20=EC=B0=BD=20=EB=92=A4=20?= =?UTF-8?q?=EB=B9=88=20.nomedia=20=EB=A7=8C=20=EB=A7=8C=EB=93=A4=EA=B3=A0?= =?UTF-8?q?=20=EC=A7=80=EC=9B=80=20-=20=EC=95=88=EC=9D=98=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B7=B8=EB=8C=80=EB=A1=9C)=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20(=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 --- assets/l10n/en.json | 14 ++++++ assets/l10n/ja.json | 14 ++++++ assets/l10n/zh-Hans.json | 14 ++++++ lib/app/settings.dart | 5 +++ lib/ui/explorer_page.dart | 89 +++++++++++++++++++++++++++++++++++++++ lib/ui/settings_page.dart | 7 +++ test/explorer_test.dart | 79 ++++++++++++++++++++++++++++++++++ 7 files changed, 222 insertions(+) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index bf5fc7a..a024a2b 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -72,6 +72,7 @@ "+30분": "+30 min", "- 감지한 언어": "- detected", "-1 은 아무렇게나": "-1 = random", + ".nomedia 를 지웠습니다. 갤러리 · 음악 앱에 다시 보입니다.": ".nomedia removed. Gallery and music apps will show it again.", ".{0} 재생 프로그램 (exe)": ".{0} player (exe)", "/ 로 시작하는 전체 경로로": "use a full path starting with /", "0 = 제한 없음. 모든 방법에 적용: rsync --bwlimit, 현재 방식은 앱이 조절, robocopy 는 /IPG 로 비슷하게. 옵션 칸에 직접 넣어도 됩니다 (그쪽이 우선)": "0 = unlimited. Applies to every method: rsync --bwlimit, the built-in method throttles itself, robocopy approximates with /IPG. You can also put it in the options field (that takes priority)", @@ -704,6 +705,9 @@ "개인 키를 넣었습니다 (원래 파일 경로는 저장하지 않습니다)": "Private key added (the original file path isn't saved)", "개인 키를 읽을 수 없습니다 (OpenSSH · PEM 형식)": "Can't read the private key (OpenSSH or PEM format)", "개인 키의 암호가 맞지 않습니다": "The private key passphrase is incorrect", + "갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)": "Show in gallery and music apps again (remove .nomedia)", + "갤러리 · 음악 앱에서 숨겼습니다 (.nomedia). 파일은 그대로입니다.": "Hidden from gallery and music apps (.nomedia). The files are unchanged.", + "갤러리 · 음악 앱에서 숨기기 (.nomedia)": "Hide from gallery and music apps (.nomedia)", "거꾸로": "Reverse", "거꾸로 된 범위: {0}": "Reversed range: {0}", "거른 것 {0}개도 지워집니다 (--delete-excluded)": "{0} excluded items will also be deleted (--delete-excluded)", @@ -988,6 +992,7 @@ "다시 만들기 대기로": "Back to waiting for remake", "다시 만들기 대기로 (지금 묶음이 끝난 뒤)": "Queue again to rebuild (after the current batch finishes)", "다시 받기": "Download again", + "다시 보이기": "Show again", "다시 비교": "Compare again", "다시 설치": "Reinstall", "다시 세기": "Recount", @@ -1027,6 +1032,7 @@ "다음 {0}개 기다림": "{0} more waiting", "다음 페이지": "Next page", "다음 항목 (→)": "Next item (→)", + "다음부터 묻지 않기": "Don't ask again", "다음부터 묻지 않기 (환경 설정 > 실행 에서 바꿀 수 있음)": "Don't ask again (can be changed in Settings > Run)", "단계": "Steps", "단어 첫 글자만 크게 (Abc)": "Capitalize words (Abc)", @@ -1251,6 +1257,7 @@ "목": "Thu", "목록": "list", "목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "Hides ad areas in lists · next to · on top of videos", + "목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.": "You can see them again with [Show hidden items] at the bottom of the list, or in Settings > File explorer.", "목록 보기": "View list", "목록 파일은 전체 경로로 적어 주세요: {0}": "Please write the full path of the list file: {0}", "목록 파일의 전체 경로를 적어 주세요": "write the full path of the list file", @@ -1261,6 +1268,7 @@ "목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "Hidden from the list ({0}). The files are unchanged - use [Show hidden items] at the bottom of the list to see them again.", "목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "Hidden from the list ({0}). The files are unchanged.", "목록에서 숨기기": "Hide from list", + "목록에서 숨기기 전에 묻기": "Ask before hiding from the list", "목록에서 숨긴 항목 ({0}개)": "Items hidden from the list ({0})", "목록에서 숨긴 항목 다시 숨기기": "Hide the hidden items again", "목록에서 숨긴 항목 보이기 ({0}개)": "Show items hidden from the list ({0})", @@ -1271,6 +1279,7 @@ "목록의 동영상 {0}개 모두": "All {0} videos in the list", "몽골어": "Mongolian", "무엇을 그릴지 적어 주세요": "Write what to draw", + "무엇이 바뀌는지 (목록에서만 빠지고 파일은 그대로) 확인 창을 띄웁니다": "Shows a dialog explaining what changes (removed from the list only; files stay as they are)", "무엇이 바뀌는지 비교하는 중…": "Comparing what will change…", "무한": "Unlimited", "묶기": "Compress", @@ -1303,6 +1312,7 @@ "바꾸기 ({0}개)": "Rename ({0})", "바꾸는 정도 {0}%": "Change amount {0}%", "바꾸면 바로 저장됩니다": "Saved automatically", + "바꾸지 못했습니다: {0}": "Could not change it: {0}", "바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "Couldn't save the converted PDF (check storage space)", "바꾼 것이 없습니다 (원본 그대로)": "Nothing changed (original as is)", "바꿀 글": "Replace with", @@ -2087,6 +2097,7 @@ "이 설치본에는 rsync 가 들어 있지 않습니다 (rsync 로 복사할 때 · 오른쪽 [내려받기] 로 받습니다)": "This install has no rsync (downloaded when copying with rsync · or use [Download] on the right)", "이 설치본에는 rsync 가 들어 있지 않습니다. 직접 지정하세요.": "This install has no rsync. Choose one yourself.", "이 앱 (APK) 에는 Tor 가 들어 있지 않습니다.": "This app (APK) does not contain Tor.", + "이 앱의 파일 탐색기 목록에서만 빠집니다. 파일은 지우거나 옮기거나 이름을 바꾸지 않고, 다른 앱 · 갤러리에서는 그대로 보입니다.": "It is removed only from this app's file explorer list. Files are not deleted, moved or renamed, and other apps and the gallery still show them.", "이 언어 지우기": "Remove this language", "이 연결은 서명도 암호화도 되지 않습니다 ({0}). 같은 네트워크의 다른 사람이 파일 내용을 보거나 바꿀 수 있으니, NAS 설정에서 SMB 서명이나 SMB3 암호화를 켜는 것이 좋습니다": "This connection is neither signed nor encrypted ({0}). Others on the same network could read or change the files, so it's best to turn on SMB signing or SMB3 encryption in the NAS settings", "이 연결은 암호화되지 않습니다 ({0}). 같은 네트워크의 다른 사람이 파일 내용을 볼 수 있으니, NAS 설정에서 SMB3 암호화를 켜는 것이 좋습니다": "This connection isn't encrypted ({0}). Others on the same network could see file contents, so it's best to turn on SMB3 encryption in the NAS settings", @@ -2112,12 +2123,14 @@ "이 폴더를 볼 권한이 없습니다": "No permission to view this folder", "이 폴더를 읽을 권한이 없습니다": "No permission to read this folder", "이 폴더에 동영상이 없습니다.": "There are no videos in this folder.", + "이 폴더에 빈 .nomedia 파일을 만듭니다. 갤러리 · 음악 앱이 이 폴더 (안의 폴더 포함) 의 그림 · 동영상 · 음악을 보이지 않게 됩니다.": "Creates an empty .nomedia file in this folder. Gallery and music apps will stop showing the pictures, videos and music in this folder (including subfolders).", "이 폴더에 새 폴더": "New folder in this folder", "이 폴더에 현재 페이지 추가": "Add the current page to this folder", "이 폴더에는 고를 파일이 없습니다": "There are no files to choose in this folder", "이 폴더에는 그림 · 동영상이 없습니다": "No pictures or videos in this folder", "이 폴더에는 그림이 없습니다": "There are no images in this folder", "이 폴더에만: {0}": "This folder only: {0}", + "이 폴더의 .nomedia 파일을 지웁니다. 갤러리 · 음악 앱이 이 폴더의 그림 · 동영상 · 음악을 다시 보이게 됩니다.": "Deletes the .nomedia file in this folder. Gallery and music apps will show the pictures, videos and music in this folder again.", "이 폴더의 동영상 재생": "Play videos in this folder", "이 폴더의 파일": "Files in this folder", "이 프로그램이 관리자 권한으로 실행 중입니다. 권장하지 않습니다.\n\n· 내장 Chrome 엔진은 샌드박스 (웹 페이지를 가두는 보호) 가 꺼져 있어, 웹 페이지가 관리자 권한 프로세스 안에서 돕니다. 페이지의 취약점이 곧 이 PC 의 관리자 권한이 될 수 있습니다.\n· Edge 엔진은 이 영향이 없습니다.\n· 관리자 권한 없이 프로그램을 실행하면 이 설정 없이도 Chrome 엔진을 쓸 수 있습니다.": "This app is running as administrator. This is not recommended.\n\n· The built-in Chrome engine has its sandbox (the protection that confines web pages) turned off, so web pages run inside an administrator process. A flaw in a page could become administrator access to this PC.\n· The Edge engine is not affected.\n· If you run the app without administrator rights, you can use the Chrome engine without this setting.", @@ -2731,6 +2744,7 @@ "폴더 수정": "Edit folder", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "Shows the total size of the files inside each folder. Large folders or a NAS can take a while to read. Long-press [Calculate folder size] always works", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "Syncs the folder contents (no same-named folder is created in the target).", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "Files in the folder are not changed. It may take a while to show in the gallery, or you may need to reopen that app.", "폴더 열기": "Open folder", "폴더 이름": "Folder name", "폴더 이름 바꾸기 · 이동": "Rename · move folder", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 32b4107..88e057b 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -72,6 +72,7 @@ "+30분": "+30分", "- 감지한 언어": "- 検出した言語", "-1 은 아무렇게나": "-1 はランダム", + ".nomedia 를 지웠습니다. 갤러리 · 음악 앱에 다시 보입니다.": ".nomedia を削除しました。ギャラリー・音楽アプリに再び表示されます。", ".{0} 재생 프로그램 (exe)": ".{0} 再生プログラム(exe)", "/ 로 시작하는 전체 경로로": "/ で始まるフルパスで", "0 = 제한 없음. 모든 방법에 적용: rsync --bwlimit, 현재 방식은 앱이 조절, robocopy 는 /IPG 로 비슷하게. 옵션 칸에 직접 넣어도 됩니다 (그쪽이 우선)": "0 = 制限なし。すべての方法に適用: rsync は --bwlimit、現在の方式はアプリが調整、robocopy は /IPG で近似。オプション欄に直接入れても構いません (そちらが優先)", @@ -704,6 +705,9 @@ "개인 키를 넣었습니다 (원래 파일 경로는 저장하지 않습니다)": "秘密鍵を登録しました (元のファイルのパスは保存しません)", "개인 키를 읽을 수 없습니다 (OpenSSH · PEM 형식)": "秘密鍵を読み込めません (OpenSSH・PEM 形式)", "개인 키의 암호가 맞지 않습니다": "秘密鍵のパスフレーズが正しくありません", + "갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)": "ギャラリー・音楽アプリに再表示 (.nomedia を削除)", + "갤러리 · 음악 앱에서 숨겼습니다 (.nomedia). 파일은 그대로입니다.": "ギャラリー・音楽アプリから隠しました (.nomedia)。ファイルはそのままです。", + "갤러리 · 음악 앱에서 숨기기 (.nomedia)": "ギャラリー・音楽アプリから隠す (.nomedia)", "거꾸로": "逆順", "거꾸로 된 범위: {0}": "逆順の範囲: {0}", "거른 것 {0}개도 지워집니다 (--delete-excluded)": "除外したもの {0} 件も削除されます (--delete-excluded)", @@ -988,6 +992,7 @@ "다시 만들기 대기로": "作り直し待ちに戻す", "다시 만들기 대기로 (지금 묶음이 끝난 뒤)": "再作成待ちに戻す (今のまとめ作業が終わった後)", "다시 받기": "再ダウンロード", + "다시 보이기": "再表示", "다시 비교": "もう一度比較", "다시 설치": "再インストール", "다시 세기": "再カウント", @@ -1027,6 +1032,7 @@ "다음 {0}개 기다림": "次の {0} 件が待機中", "다음 페이지": "次のページ", "다음 항목 (→)": "次の項目 (→)", + "다음부터 묻지 않기": "次回から確認しない", "다음부터 묻지 않기 (환경 설정 > 실행 에서 바꿀 수 있음)": "次回から確認しない(環境設定 > 実行 で変更できます)", "단계": "ステップ", "단어 첫 글자만 크게 (Abc)": "単語の先頭だけ大文字 (Abc)", @@ -1251,6 +1257,7 @@ "목": "木", "목록": "一覧", "목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "リスト・動画の横・動画の上に出る広告エリアを隠します", + "목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.": "リスト最下部の [隠した項目を表示] または 設定 > ファイルエクスプローラー で再び表示できます。", "목록 보기": "リストを見る", "목록 파일은 전체 경로로 적어 주세요: {0}": "リストファイルはフルパスで書いてください: {0}", "목록 파일의 전체 경로를 적어 주세요": "リストファイルのフルパスを書いてください", @@ -1261,6 +1268,7 @@ "목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "リストから隠しました ({0} 件)。ファイルはそのままで、リストの一番下の [隠した項目を表示] で再び見られます。", "목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "リストから隠しました ({0} 件)。ファイルはそのままです。", "목록에서 숨기기": "リストから隠す", + "목록에서 숨기기 전에 묻기": "リストから隠す前に確認", "목록에서 숨긴 항목 ({0}개)": "リストから隠した項目 ({0} 件)", "목록에서 숨긴 항목 다시 숨기기": "リストから隠した項目を再び隠す", "목록에서 숨긴 항목 보이기 ({0}개)": "リストから隠した項目を表示 ({0} 件)", @@ -1271,6 +1279,7 @@ "목록의 동영상 {0}개 모두": "リストの動画 {0}個すべて", "몽골어": "モンゴル語", "무엇을 그릴지 적어 주세요": "何を描くか入力してください", + "무엇이 바뀌는지 (목록에서만 빠지고 파일은 그대로) 확인 창을 띄웁니다": "何が変わるか (リストから外れるだけでファイルはそのまま) の確認画面を表示します", "무엇이 바뀌는지 비교하는 중…": "何が変わるか比較中…", "무한": "無制限", "묶기": "まとめる", @@ -1303,6 +1312,7 @@ "바꾸기 ({0}개)": "変更 ({0} 件)", "바꾸는 정도 {0}%": "変える度合い {0}%", "바꾸면 바로 저장됩니다": "変更するとすぐ保存されます", + "바꾸지 못했습니다: {0}": "変更できませんでした: {0}", "바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "変換した PDF を保存できませんでした (空き容量を確認してください)", "바꾼 것이 없습니다 (원본 그대로)": "変更なし(元のまま)", "바꿀 글": "置き換える文字", @@ -2087,6 +2097,7 @@ "이 설치본에는 rsync 가 들어 있지 않습니다 (rsync 로 복사할 때 · 오른쪽 [내려받기] 로 받습니다)": "このインストールには rsync が含まれていません (rsync でコピーするとき · 右の [ダウンロード] で取得します)", "이 설치본에는 rsync 가 들어 있지 않습니다. 직접 지정하세요.": "このインストールには rsync が含まれていません。直接指定してください。", "이 앱 (APK) 에는 Tor 가 들어 있지 않습니다.": "このアプリ (APK) には Tor が入っていません。", + "이 앱의 파일 탐색기 목록에서만 빠집니다. 파일은 지우거나 옮기거나 이름을 바꾸지 않고, 다른 앱 · 갤러리에서는 그대로 보입니다.": "このアプリのファイルエクスプローラーの一覧から外れるだけです。ファイルは削除・移動・名前変更されず、他のアプリやギャラリーではそのまま表示されます。", "이 언어 지우기": "この言語を削除", "이 연결은 서명도 암호화도 되지 않습니다 ({0}). 같은 네트워크의 다른 사람이 파일 내용을 보거나 바꿀 수 있으니, NAS 설정에서 SMB 서명이나 SMB3 암호화를 켜는 것이 좋습니다": "この接続は署名も暗号化もされません ({0})。同じネットワークの他の人がファイルの内容を見たり変えたりできるため、NAS の設定で SMB 署名か SMB3 暗号化をオンにすることをおすすめします", "이 연결은 암호화되지 않습니다 ({0}). 같은 네트워크의 다른 사람이 파일 내용을 볼 수 있으니, NAS 설정에서 SMB3 암호화를 켜는 것이 좋습니다": "この接続は暗号化されていません ({0})。同じネットワークの他の人がファイルの内容を見られるため、NAS の設定で SMB3 暗号化をオンにすることをお勧めします", @@ -2112,12 +2123,14 @@ "이 폴더를 볼 권한이 없습니다": "このフォルダーを表示する権限がありません", "이 폴더를 읽을 권한이 없습니다": "このフォルダーを読む権限がありません", "이 폴더에 동영상이 없습니다.": "このフォルダーに動画がありません。", + "이 폴더에 빈 .nomedia 파일을 만듭니다. 갤러리 · 음악 앱이 이 폴더 (안의 폴더 포함) 의 그림 · 동영상 · 음악을 보이지 않게 됩니다.": "このフォルダーに空の .nomedia ファイルを作ります。ギャラリー・音楽アプリはこのフォルダー (サブフォルダーを含む) の画像・動画・音楽を表示しなくなります。", "이 폴더에 새 폴더": "このフォルダーに新しいフォルダー", "이 폴더에 현재 페이지 추가": "このフォルダーに現在のページを追加", "이 폴더에는 고를 파일이 없습니다": "このフォルダーには選べるファイルがありません", "이 폴더에는 그림 · 동영상이 없습니다": "このフォルダーには画像・動画がありません", "이 폴더에는 그림이 없습니다": "このフォルダーには画像がありません", "이 폴더에만: {0}": "このフォルダーだけ: {0}", + "이 폴더의 .nomedia 파일을 지웁니다. 갤러리 · 음악 앱이 이 폴더의 그림 · 동영상 · 음악을 다시 보이게 됩니다.": "このフォルダーの .nomedia ファイルを削除します。ギャラリー・音楽アプリはこのフォルダーの画像・動画・音楽を再び表示します。", "이 폴더의 동영상 재생": "このフォルダーの動画を再生", "이 폴더의 파일": "このフォルダーのファイル", "이 프로그램이 관리자 권한으로 실행 중입니다. 권장하지 않습니다.\n\n· 내장 Chrome 엔진은 샌드박스 (웹 페이지를 가두는 보호) 가 꺼져 있어, 웹 페이지가 관리자 권한 프로세스 안에서 돕니다. 페이지의 취약점이 곧 이 PC 의 관리자 권한이 될 수 있습니다.\n· Edge 엔진은 이 영향이 없습니다.\n· 관리자 권한 없이 프로그램을 실행하면 이 설정 없이도 Chrome 엔진을 쓸 수 있습니다.": "このアプリは管理者権限で実行されています。おすすめしません。\n\n· 内蔵 Chrome エンジンはサンドボックス (Web ページを閉じ込める保護) がオフのため、Web ページが管理者権限のプロセス内で動きます。ページの脆弱性がそのままこの PC の管理者権限になるおそれがあります。\n· Edge エンジンにはこの影響がありません。\n· 管理者権限なしでアプリを実行すれば、この設定なしで Chrome エンジンを使えます。", @@ -2731,6 +2744,7 @@ "폴더 수정": "フォルダーを編集", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "フォルダー内のファイルサイズの合計を一覧に表示します。大きなフォルダー・NAS は読み込みに時間がかかることがあります。長押しの [フォルダーサイズを計算] はいつでも使えます", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "フォルダーの中身を合わせます (対象に同じ名前のフォルダーは作りません)。", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "フォルダー内のファイルは変更しません。ギャラリーに反映されるまで時間がかかったり、そのアプリを開き直す必要がある場合があります。", "폴더 열기": "フォルダーを開く", "폴더 이름": "フォルダー名", "폴더 이름 바꾸기 · 이동": "フォルダー名の変更・移動", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 159cd9d..01be808 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -72,6 +72,7 @@ "+30분": "+30分钟", "- 감지한 언어": "- 检测到的语言", "-1 은 아무렇게나": "-1 为随机", + ".nomedia 를 지웠습니다. 갤러리 · 음악 앱에 다시 보입니다.": "已删除 .nomedia。图库和音乐应用将重新显示。", ".{0} 재생 프로그램 (exe)": ".{0} 播放程序(exe)", "/ 로 시작하는 전체 경로로": "请使用以 / 开头的完整路径", "0 = 제한 없음. 모든 방법에 적용: rsync --bwlimit, 현재 방식은 앱이 조절, robocopy 는 /IPG 로 비슷하게. 옵션 칸에 직접 넣어도 됩니다 (그쪽이 우선)": "0 = 不限制。适用于所有方式: rsync 用 --bwlimit,当前方式由应用调节,robocopy 用 /IPG 近似。也可直接写在选项栏中 (优先)", @@ -704,6 +705,9 @@ "개인 키를 넣었습니다 (원래 파일 경로는 저장하지 않습니다)": "已添加私钥 (不保存原文件路径)", "개인 키를 읽을 수 없습니다 (OpenSSH · PEM 형식)": "无法读取私钥 (OpenSSH · PEM 格式)", "개인 키의 암호가 맞지 않습니다": "私钥密码不正确", + "갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)": "在图库和音乐应用中重新显示 (删除 .nomedia)", + "갤러리 · 음악 앱에서 숨겼습니다 (.nomedia). 파일은 그대로입니다.": "已在图库和音乐应用中隐藏 (.nomedia)。文件保持不变。", + "갤러리 · 음악 앱에서 숨기기 (.nomedia)": "在图库和音乐应用中隐藏 (.nomedia)", "거꾸로": "倒序", "거꾸로 된 범위: {0}": "范围顺序颠倒: {0}", "거른 것 {0}개도 지워집니다 (--delete-excluded)": "已排除的 {0} 项也会被删除 (--delete-excluded)", @@ -988,6 +992,7 @@ "다시 만들기 대기로": "改回等待重新制作", "다시 만들기 대기로 (지금 묶음이 끝난 뒤)": "重新排队制作 (当前批次结束后)", "다시 받기": "重新下载", + "다시 보이기": "重新显示", "다시 비교": "重新比较", "다시 설치": "重新安装", "다시 세기": "重新统计", @@ -1027,6 +1032,7 @@ "다음 {0}개 기다림": "另有 {0} 个等待中", "다음 페이지": "下一页", "다음 항목 (→)": "下一项 (→)", + "다음부터 묻지 않기": "以后不再询问", "다음부터 묻지 않기 (환경 설정 > 실행 에서 바꿀 수 있음)": "下次不再询问(可在 设置 > 运行 中更改)", "단계": "步数", "단어 첫 글자만 크게 (Abc)": "单词首字母大写 (Abc)", @@ -1251,6 +1257,7 @@ "목": "四", "목록": "列表", "목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "隐藏列表 · 视频旁 · 视频上方出现的广告区域", + "목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.": "可通过列表底部的 [显示隐藏项] 或 设置 > 文件管理器 重新查看。", "목록 보기": "查看列表", "목록 파일은 전체 경로로 적어 주세요: {0}": "请填写列表文件的完整路径: {0}", "목록 파일의 전체 경로를 적어 주세요": "请填写列表文件的完整路径", @@ -1261,6 +1268,7 @@ "목록에서 숨겼습니다 ({0}개). 파일은 그대로이고, 목록 맨 아래의 [숨긴 항목 보이기] 로 다시 볼 수 있습니다.": "已从列表中隐藏 ({0} 项)。文件本身未改变,可用列表底部的 [显示已隐藏项] 再次查看。", "목록에서 숨겼습니다 ({0}개). 파일은 그대로입니다.": "已从列表中隐藏 ({0} 项)。文件本身未改变。", "목록에서 숨기기": "从列表中隐藏", + "목록에서 숨기기 전에 묻기": "从列表中隐藏前询问", "목록에서 숨긴 항목 ({0}개)": "从列表中隐藏的项 ({0} 项)", "목록에서 숨긴 항목 다시 숨기기": "再次隐藏已从列表中隐藏的项", "목록에서 숨긴 항목 보이기 ({0}개)": "显示从列表中隐藏的项 ({0} 项)", @@ -1271,6 +1279,7 @@ "목록의 동영상 {0}개 모두": "列表中的全部 {0} 个视频", "몽골어": "蒙古语", "무엇을 그릴지 적어 주세요": "请输入要画的内容", + "무엇이 바뀌는지 (목록에서만 빠지고 파일은 그대로) 확인 창을 띄웁니다": "显示确认窗口,说明会发生什么 (仅从列表中移除,文件不变)", "무엇이 바뀌는지 비교하는 중…": "正在比较将要更改的内容…", "무한": "无限制", "묶기": "压缩", @@ -1303,6 +1312,7 @@ "바꾸기 ({0}개)": "重命名 ({0} 个)", "바꾸는 정도 {0}%": "改变程度 {0}%", "바꾸면 바로 저장됩니다": "更改后立即保存", + "바꾸지 못했습니다: {0}": "无法更改: {0}", "바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "无法保存转换后的 PDF (请检查存储空间)", "바꾼 것이 없습니다 (원본 그대로)": "未做更改(保持原样)", "바꿀 글": "替换为", @@ -2087,6 +2097,7 @@ "이 설치본에는 rsync 가 들어 있지 않습니다 (rsync 로 복사할 때 · 오른쪽 [내려받기] 로 받습니다)": "此安装中没有 rsync (使用 rsync 复制时 · 或用右侧 [下载] 获取)", "이 설치본에는 rsync 가 들어 있지 않습니다. 직접 지정하세요.": "此安装中没有 rsync。请自行指定。", "이 앱 (APK) 에는 Tor 가 들어 있지 않습니다.": "此应用 (APK) 未包含 Tor。", + "이 앱의 파일 탐색기 목록에서만 빠집니다. 파일은 지우거나 옮기거나 이름을 바꾸지 않고, 다른 앱 · 갤러리에서는 그대로 보입니다.": "仅从本应用的文件管理器列表中移除。文件不会被删除、移动或重命名,其他应用和图库中仍然可见。", "이 언어 지우기": "删除此语言", "이 연결은 서명도 암호화도 되지 않습니다 ({0}). 같은 네트워크의 다른 사람이 파일 내용을 보거나 바꿀 수 있으니, NAS 설정에서 SMB 서명이나 SMB3 암호화를 켜는 것이 좋습니다": "此连接既未签名也未加密 ({0})。同一网络中的其他人可能查看或篡改文件内容,建议在 NAS 设置中开启 SMB 签名或 SMB3 加密", "이 연결은 암호화되지 않습니다 ({0}). 같은 네트워크의 다른 사람이 파일 내용을 볼 수 있으니, NAS 설정에서 SMB3 암호화를 켜는 것이 좋습니다": "此连接未加密 ({0})。同一网络中的其他人可能看到文件内容,建议在 NAS 设置中开启 SMB3 加密", @@ -2112,12 +2123,14 @@ "이 폴더를 볼 권한이 없습니다": "没有查看此文件夹的权限", "이 폴더를 읽을 권한이 없습니다": "没有读取此文件夹的权限", "이 폴더에 동영상이 없습니다.": "此文件夹中没有视频。", + "이 폴더에 빈 .nomedia 파일을 만듭니다. 갤러리 · 음악 앱이 이 폴더 (안의 폴더 포함) 의 그림 · 동영상 · 음악을 보이지 않게 됩니다.": "在此文件夹中创建空的 .nomedia 文件。图库和音乐应用将不再显示此文件夹 (含子文件夹) 中的图片、视频和音乐。", "이 폴더에 새 폴더": "在此文件夹中新建文件夹", "이 폴더에 현재 페이지 추가": "将当前页面添加到此文件夹", "이 폴더에는 고를 파일이 없습니다": "此文件夹中没有可选择的文件", "이 폴더에는 그림 · 동영상이 없습니다": "此文件夹中没有图片或视频", "이 폴더에는 그림이 없습니다": "此文件夹中没有图片", "이 폴더에만: {0}": "仅此文件夹: {0}", + "이 폴더의 .nomedia 파일을 지웁니다. 갤러리 · 음악 앱이 이 폴더의 그림 · 동영상 · 음악을 다시 보이게 됩니다.": "删除此文件夹中的 .nomedia 文件。图库和音乐应用将重新显示此文件夹中的图片、视频和音乐。", "이 폴더의 동영상 재생": "播放此文件夹中的视频", "이 폴더의 파일": "此文件夹中的文件", "이 프로그램이 관리자 권한으로 실행 중입니다. 권장하지 않습니다.\n\n· 내장 Chrome 엔진은 샌드박스 (웹 페이지를 가두는 보호) 가 꺼져 있어, 웹 페이지가 관리자 권한 프로세스 안에서 돕니다. 페이지의 취약점이 곧 이 PC 의 관리자 권한이 될 수 있습니다.\n· Edge 엔진은 이 영향이 없습니다.\n· 관리자 권한 없이 프로그램을 실행하면 이 설정 없이도 Chrome 엔진을 쓸 수 있습니다.": "本应用正以管理员权限运行,不推荐这样做。\n\n· 内置 Chrome 引擎的沙盒(限制网页的保护)已关闭,网页会在管理员权限进程中运行。网页中的漏洞可能直接变成此电脑的管理员权限。\n· Edge 引擎不受此影响。\n· 不以管理员权限运行本应用时,无需此设置即可使用 Chrome 引擎。", @@ -2731,6 +2744,7 @@ "폴더 수정": "编辑文件夹", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "在列表中显示文件夹内文件大小的总和。大型文件夹或 NAS 可能需要较长时间读取。长按 [计算文件夹大小] 始终可用", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "同步文件夹内的内容 (不会在目标中新建同名文件夹)。", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "不会更改文件夹中的文件。图库可能需要一段时间才能反映,或需要重新打开该应用。", "폴더 열기": "打开文件夹", "폴더 이름": "文件夹名称", "폴더 이름 바꾸기 · 이동": "重命名 · 移动文件夹", diff --git a/lib/app/settings.dart b/lib/app/settings.dart index 88463fb..1b828e4 100644 --- a/lib/app/settings.dart +++ b/lib/app/settings.dart @@ -490,6 +490,9 @@ class AppSettings { /// 지우기 · 이동 대상에서는 빠지지 않는다. 목록 맨 아래 [숨긴 항목 보이기] 로 다시 본다 List explorerHiddenItems = []; + /// X-plore 맞추기 (관리자 지시 10/20): [목록에서 숨기기] 전에 무엇이 바뀌는지 확인 창 (처음 켬, 창의 [다음부터 묻지 않기] · 환경 설정에서 끔) + bool explorerHideConfirm = true; + // ── 복사 · 이동 (파일 탐색기) · rsync · lsync (Rsync 화면) ── /// 파일 탐색기의 복사 · 이동 방법: 'builtin' 현재 방식 (기본) · 'robocopy' (Windows). 파일만 고를 때 / 폴더가 들어 있을 때. /// rsync 는 Rsync 화면에서 따로 (파일 탐색기에서는 쓰지 않음) @@ -856,6 +859,7 @@ class AppSettings { 'explorerFolderSorts': explorerFolderSorts, 'explorerShowHidden': explorerShowHidden, 'explorerHiddenItems': explorerHiddenItems, + 'explorerHideConfirm': explorerHideConfirm, 'explorerPaths': explorerPaths, 'copyMethodFile': copyMethodFile, 'copyConflict': copyConflict, @@ -1102,6 +1106,7 @@ class AppSettings { } ..explorerShowHidden = j['explorerShowHidden'] as bool? ?? false ..explorerHiddenItems = [for (final x in (j['explorerHiddenItems'] as List?) ?? const []) if (x is String && x.isNotEmpty) x] + ..explorerHideConfirm = j['explorerHideConfirm'] as bool? ?? true ..explorerPaths = [for (final x in (j['explorerPaths'] as List?) ?? const []) '$x'] ..copyMethodFile = _method(j['copyMethodFile']) ..copyConflict = const ['rename', 'overwrite', 'skip'].contains(j['copyConflict']) ? j['copyConflict'] as String : 'ask' diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 2d55362..e7dbbb7 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -115,6 +115,10 @@ class ExplorerPage extends StatefulWidget { @visibleForTesting static bool? debugAndroidTrash; + /// 시험: 폴더 .nomedia 넣기 · 지우기 (Android 갤러리 · 음악 앱에서 숨기기) 를 Windows 에서. null 이면 Android 만 + @visibleForTesting + static bool? debugNomedia; + /// 시험: 경로 입력 창이 넣은 경로를 고쳐 쓰고 확인하는 것 (갈 경로, 칸 아래 이유) @visibleForTesting static Future<(String?, String?)> checkPathForTest(String input) => _ExplorerPageState.checkEnteredPath(input); @@ -2040,6 +2044,11 @@ class _ExplorerPageState extends State { targets.every(_appHidden) ? ('appUnhide', Icons.visibility_outlined, many ? trf('{0}개 숨기기 풀기', [targets.length]) : tr('숨기기 풀기')) : ('appHide', Icons.visibility_off_outlined, many ? trf('{0}개 목록에서 숨기기', [targets.length]) : tr('목록에서 숨기기')), + // X-plore 맞추기 (관리자 지시 10/20): 폴더에 .nomedia 를 두면 갤러리 · 음악 앱이 그 안의 그림 · 동영상 · 음악을 보이지 않는다 (Android) + if (e.isDir && !dav && !inArchive && (ExplorerPage.debugNomedia ?? Platform.isAndroid)) + File(p.join(e.path, '.nomedia')).existsSync() + ? ('nomediaOff', Icons.photo_library_outlined, tr('갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)')) + : ('nomediaOn', Icons.hide_image_outlined, tr('갤러리 · 음악 앱에서 숨기기 (.nomedia)')), // 두 창이면 아이콘 줄의 복사 · 이동은 다른 창으로 - 담아 두었다가 원하는 폴더에 붙여넣기는 여기 if (dual) ('clipCopy', Icons.content_copy, tr('복사 (붙여넣기로)')), if (dual) ('clipMove', Icons.content_cut, tr('이동 (붙여넣기로)')), @@ -2138,6 +2147,7 @@ class _ExplorerPageState extends State { pane.changed(); } case 'appHide': + if (c.settings.explorerHideConfirm && !await _confirmAppHide(targets)) return; await c.updateSettings((s) => s.explorerHiddenItems = [ ...s.explorerHiddenItems, for (final x in targets) @@ -2161,6 +2171,8 @@ class _ExplorerPageState extends State { q.changed(); } if (mounted) setState(() {}); + case 'nomediaOn' || 'nomediaOff': + await _nomedia(pane, e.path, on: pick == 'nomediaOn'); case 'props': await showFolderProperties(context, e.path); case 'hash': @@ -4039,6 +4051,83 @@ class _ExplorerPageState extends State { ); } + /// X-plore 맞추기 (관리자 지시 10/20): [목록에서 숨기기] 전에 무엇이 바뀌는지 알림. [다음부터 묻지 않기] 는 환경 설정에서 다시 켬 + Future _confirmAppHide(List targets) async { + var dontAsk = false; + final ok = await showDialog( + context: context, + builder: (ctx) => StatefulBuilder( + builder: (ctx, set) => AlertDialog( + scrollable: true, + title: Text(targets.length > 1 ? trf('{0}개 목록에서 숨기기', [targets.length]) : tr('목록에서 숨기기')), + content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (targets.length == 1) Text(vBasename(targets.single), style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 6), + Text(tr('이 앱의 파일 탐색기 목록에서만 빠집니다. 파일은 지우거나 옮기거나 이름을 바꾸지 않고, 다른 앱 · 갤러리에서는 그대로 보입니다.')), + const SizedBox(height: 6), + Text(tr('목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.'), + style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + CheckboxListTile( + contentPadding: EdgeInsets.zero, + controlAffinity: ListTileControlAffinity.leading, + value: dontAsk, + onChanged: (v) => set(() => dontAsk = v ?? false), + title: Text(tr('다음부터 묻지 않기')), + ), + ]), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), + FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(tr('숨기기'))), + ], + ), + ), + ); + if (ok != true) return false; + if (dontAsk) await c.updateSettings((s) => s.explorerHideConfirm = false); + return true; + } + + /// X-plore 맞추기 (관리자 지시 10/20): 폴더에 빈 .nomedia 파일을 넣거나 지운다 (Android 갤러리 · 음악 앱에서 숨기기 · 다시 보이기). + /// 그 폴더의 파일은 그대로 - 무엇이 바뀌는지 먼저 묻는다. 갤러리에 반영되는 때는 그 앱이 다시 읽을 때다 + Future _nomedia(_Pane pane, String dir, {required bool on}) async { + final ok = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + scrollable: true, + title: Text(on ? tr('갤러리 · 음악 앱에서 숨기기 (.nomedia)') : tr('갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)')), + content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(vBasename(dir), style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 6), + Text(on + ? tr('이 폴더에 빈 .nomedia 파일을 만듭니다. 갤러리 · 음악 앱이 이 폴더 (안의 폴더 포함) 의 그림 · 동영상 · 음악을 보이지 않게 됩니다.') + : tr('이 폴더의 .nomedia 파일을 지웁니다. 갤러리 · 음악 앱이 이 폴더의 그림 · 동영상 · 음악을 다시 보이게 됩니다.')), + const SizedBox(height: 6), + Text(tr('폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.'), + style: const TextStyle(fontSize: 12, color: JjColors.textDim)), + ]), + actions: [ + TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(tr('취소'))), + FilledButton(onPressed: () => Navigator.pop(ctx, true), child: Text(on ? tr('숨기기') : tr('다시 보이기'))), + ], + ), + ); + if (ok != true || !mounted) return; + final f = File(p.join(dir, '.nomedia')); + try { + if (on) { + if (!await f.exists()) await f.writeAsBytes(const []); + } else if (await f.exists()) { + await f.delete(); + } + } catch (err) { + _snack(trf('바꾸지 못했습니다: {0}', [err])); + return; + } + await _refreshAll([dir]); + pane.changed(); + _snack(on ? tr('갤러리 · 음악 앱에서 숨겼습니다 (.nomedia). 파일은 그대로입니다.') : tr('.nomedia 를 지웠습니다. 갤러리 · 음악 앱에 다시 보입니다.')); + } + Future _sortDialog() async { // 84: "폴더마다 기억" 이면 지금 창의 폴더 정렬 final dir = _pane.current; diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 676b9d5..fb538df 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -904,6 +904,13 @@ class _SettingsPageState extends State { enabled: s.explorerHiddenItems.isNotEmpty, onTap: () => showHiddenItems(context, c), ), + // X-plore 맞추기 (관리자 지시 10/20): 숨기기 전 확인 창 + SwitchListTile( + title: Text(tr('목록에서 숨기기 전에 묻기')), + subtitle: Text(tr('무엇이 바뀌는지 (목록에서만 빠지고 파일은 그대로) 확인 창을 띄웁니다')), + value: s.explorerHideConfirm, + onChanged: (v) => c.updateSettings((x) => x.explorerHideConfirm = v), + ), // X-plore 비교 a: 한 창일 때 복사 · 이동이 갈 곳 SettingTile( title: Text(tr('한 창일 때 복사 · 이동')), diff --git a/test/explorer_test.dart b/test/explorer_test.dart index 1f49172..a5a2f87 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -1688,6 +1688,10 @@ void main() { await tester.longPress(find.text('doc.txt')); await calm(tester); await act(tester, () => tester.tap(find.text('목록에서 숨기기'))); + await calm(tester); + // X-plore 맞추기 (관리자 지시 10/20): 무엇이 바뀌는지 확인 창 + expect(find.textContaining('파일 탐색기 목록에서만 빠집니다'), findsOneWidget); + await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '숨기기'))); await settle(tester, () => find.text('doc.txt').evaluate().isEmpty); expect(find.text('doc.txt'), findsNothing); expect(File(doc).existsSync(), isTrue, reason: '파일은 바꾸지 않는다'); @@ -1710,6 +1714,81 @@ void main() { expect(find.ancestor(of: find.text('doc.txt'), matching: find.byType(Opacity)), findsNothing); }); + testWidgets('X-plore 숨기기 확인 창: [취소] 면 그대로 · [다음부터 묻지 않기] 를 고르면 다음엔 묻지 않고 바로 숨김 (설정 저장)', (tester) async { + c.settings.explorerLayout = 'split'; + expect(AppSettings().explorerHideConfirm, isTrue, reason: '처음은 묻기'); + File(p.join(left, 'two.txt')).writeAsStringSync('2'); + await open(tester); + await tester.longPress(find.text('doc.txt')); + await calm(tester); + await act(tester, () => tester.tap(find.text('목록에서 숨기기'))); + await calm(tester); + await act(tester, () => tester.tap(find.text('취소'))); + await calm(tester); + expect(c.settings.explorerHiddenItems, isEmpty, reason: '취소하면 숨기지 않는다'); + expect(find.text('doc.txt'), findsOneWidget); + // 다음부터 묻지 않기 + 숨기기 + await tester.longPress(find.text('doc.txt')); + await calm(tester); + await act(tester, () => tester.tap(find.text('목록에서 숨기기'))); + await calm(tester); + await act(tester, () => tester.tap(find.text('다음부터 묻지 않기'))); + await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '숨기기'))); + await settle(tester, () => find.text('doc.txt').evaluate().isEmpty); + expect(c.settings.explorerHideConfirm, isFalse); + expect(AppSettings.fromJson(c.settings.toJson()).explorerHideConfirm, isFalse); + expect(AppSettings.fromJson({}).explorerHideConfirm, isTrue, reason: '예전 설정 (값 없음) 은 묻기'); + // 이제는 창 없이 바로 + await tester.longPress(find.text('two.txt')); + await calm(tester); + await act(tester, () => tester.tap(find.text('목록에서 숨기기'))); + await settle(tester, () => find.text('two.txt').evaluate().isEmpty); + expect(find.textContaining('파일 탐색기 목록에서만 빠집니다'), findsNothing); + expect(c.settings.explorerHiddenItems, [p.join(left, 'doc.txt'), p.join(left, 'two.txt')]); + }); + + testWidgets('X-plore 맞추기: 폴더 길게 누르기 [갤러리 · 음악 앱에서 숨기기 (.nomedia)] - 확인 창 뒤 빈 .nomedia, 다시 누르면 지우기 · 안의 파일은 그대로', (tester) async { + ExplorerPage.debugNomedia = true; + addTearDown(() => ExplorerPage.debugNomedia = null); + final mark = File(p.join(left, 'sub', '.nomedia')); + await open(tester); + expect(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'), findsNothing); + await tester.longPress(find.text('sub')); + await calm(tester); + await act(tester, () => tester.tap(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'))); + await calm(tester); + expect(find.textContaining('빈 .nomedia 파일을 만듭니다'), findsOneWidget); + // 취소하면 아무것도 안 바뀜 + await act(tester, () => tester.tap(find.text('취소'))); + await calm(tester); + expect(mark.existsSync(), isFalse); + await tester.longPress(find.text('sub')); + await calm(tester); + await act(tester, () => tester.tap(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'))); + await calm(tester); + await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '숨기기'))); + await settle(tester, () => mark.existsSync()); + expect(mark.lengthSync(), 0); + expect(File(p.join(left, 'sub', 'inner.txt')).readAsStringSync(), 'inner', reason: '안의 파일은 그대로'); + // 이제 메뉴는 [다시 보이기] + await idle(tester); + await tester.longPress(find.text('sub')); + await calm(tester); + expect(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'), findsNothing); + await act(tester, () => tester.tap(find.text('갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)'))); + await calm(tester); + expect(find.textContaining('.nomedia 파일을 지웁니다'), findsOneWidget); + await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '다시 보이기'))); + await settle(tester, () => !mark.existsSync()); + expect(File(p.join(left, 'sub', 'inner.txt')).existsSync(), isTrue); + // 파일에는 없음 + await idle(tester); + await tester.longPress(find.text('doc.txt')); + await calm(tester); + expect(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'), findsNothing); + expect(find.text('갤러리 · 음악 앱에 다시 보이기 (.nomedia 지우기)'), findsNothing); + }); + testWidgets('X-plore 숨기기: 숨긴 것이 든 폴더를 지울 때 확인 창에 알림 (숨긴 것도 함께 지워진다)', (tester) async { c.settings.explorerHiddenItems = [p.join(left, 'sub', 'inner.txt')]; await open(tester); From 52b3f7e4d13da1dfbea42ffb3e1e319c8145857d Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:06:55 +0900 Subject: [PATCH 16/24] =?UTF-8?q?WIP=20.nomedia=20=EB=92=A4=20=EA=B0=A4?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EB=8B=A4=EC=8B=9C=20=EC=9D=BD=EA=B8=B0=20?= =?UTF-8?q?(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20,=20?= =?UTF-8?q?=ED=8C=90=EB=8B=A8=20=E2=91=A1=20=EB=B0=94=EA=BF=88):=20?= =?UTF-8?q?=EB=84=A3=EC=9D=84=20=EB=95=8C=20=C2=B7=20=EC=A7=80=EC=9A=B8=20?= =?UTF-8?q?=EB=95=8C=20=EB=AA=A8=EB=91=90=20.nomedia=20=EC=99=80=20?= =?UTF-8?q?=EA=B7=B8=20=ED=8F=B4=EB=8D=94=EB=A5=BC=20MediaScannerConnectio?= =?UTF-8?q?n.scanFile=20=EB=A1=9C=20(MainActivity=20"mediaScan",=20AppShel?= =?UTF-8?q?l.mediaScan=20-=20Windows=20=EB=8A=94=20=ED=95=A0=20=EC=9D=BC?= =?UTF-8?q?=20=EC=97=86=EC=9D=8C,=20=EC=8B=A4=ED=8C=A8=ED=95=B4=EB=8F=84?= =?UTF-8?q?=20=EC=A1=B0=EC=9A=A9=ED=9E=88)=20=C2=B7=20=EC=95=88=EB=82=B4?= =?UTF-8?q?=20=EA=B8=80=20"=EB=B0=98=EC=98=81=EB=90=98=EA=B8=B0=EA=B9=8C?= =?UTF-8?q?=EC=A7=80=20=EC=9E=A0=EA=B9=90=20=EA=B1=B8=EB=A6=B4=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EC=8A=B5=EB=8B=88=EB=8B=A4"=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20(=EB=84=A3=EA=B3=A0=20=EC=A7=80=EC=9A=B8=20?= =?UTF-8?q?=EB=95=8C=20=EB=B6=80=EB=A6=84)=20(=EA=B2=80=ED=86=A0=20?= =?UTF-8?q?=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 --- .../src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt | 9 +++++++++ assets/l10n/en.json | 2 +- assets/l10n/ja.json | 2 +- assets/l10n/zh-Hans.json | 2 +- lib/platform/android/android_shell.dart | 9 +++++++++ lib/platform/windows/desktop_shell.dart | 4 ++++ lib/services/app_shell.dart | 6 ++++++ lib/ui/explorer_page.dart | 4 +++- test/explorer_test.dart | 10 +++++++++- 9 files changed, 43 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt index 64dd537..a5e2399 100644 --- a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt +++ b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.media.MediaScannerConnection import android.net.Uri import android.os.Build import android.os.Environment @@ -165,6 +166,14 @@ class MainActivity : FlutterActivity() { Build.VERSION.SDK_INT < Build.VERSION_CODES.O || packageManager.canRequestPackageInstalls() ) "openFolder" -> result.success(openFolder(call.argument("path") ?: "")) + // X-plore 맞추기 (관리자 지시 10/20): .nomedia 를 넣고 지운 뒤 갤러리 · 음악 앱이 다시 읽게 (실패해도 조용히) + "mediaScan" -> result.success(try { + val paths = call.argument>("paths") ?: emptyList() + if (paths.isNotEmpty()) MediaScannerConnection.scanFile(applicationContext, paths.toTypedArray(), null, null) + true + } catch (e: Exception) { + false + }) // 예전 버전으로 되돌리기: Android 는 낮은 버전을 위에 설치하지 못해 앱을 지우는 확인 창을 연다 "uninstallSelf" -> { startActivity(Intent(Intent.ACTION_DELETE, Uri.parse("package:$packageName")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index a024a2b..c7fdc4d 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -2744,7 +2744,7 @@ "폴더 수정": "Edit folder", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "Shows the total size of the files inside each folder. Large folders or a NAS can take a while to read. Long-press [Calculate folder size] always works", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "Syncs the folder contents (no same-named folder is created in the target).", - "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "Files in the folder are not changed. It may take a while to show in the gallery, or you may need to reopen that app.", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.": "Files in the folder are not changed. It may take a moment to show in the gallery.", "폴더 열기": "Open folder", "폴더 이름": "Folder name", "폴더 이름 바꾸기 · 이동": "Rename · move folder", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 88e057b..a36da9e 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -2744,7 +2744,7 @@ "폴더 수정": "フォルダーを編集", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "フォルダー内のファイルサイズの合計を一覧に表示します。大きなフォルダー・NAS は読み込みに時間がかかることがあります。長押しの [フォルダーサイズを計算] はいつでも使えます", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "フォルダーの中身を合わせます (対象に同じ名前のフォルダーは作りません)。", - "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "フォルダー内のファイルは変更しません。ギャラリーに反映されるまで時間がかかったり、そのアプリを開き直す必要がある場合があります。", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.": "フォルダー内のファイルは変更しません。ギャラリーに反映されるまで少しかかる場合があります。", "폴더 열기": "フォルダーを開く", "폴더 이름": "フォルダー名", "폴더 이름 바꾸기 · 이동": "フォルダー名の変更・移動", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 01be808..22d5464 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -2744,7 +2744,7 @@ "폴더 수정": "编辑文件夹", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "在列表中显示文件夹内文件大小的总和。大型文件夹或 NAS 可能需要较长时间读取。长按 [计算文件夹大小] 始终可用", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "同步文件夹内的内容 (不会在目标中新建同名文件夹)。", - "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "不会更改文件夹中的文件。图库可能需要一段时间才能反映,或需要重新打开该应用。", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.": "不会更改文件夹中的文件。图库可能需要片刻才能反映。", "폴더 열기": "打开文件夹", "폴더 이름": "文件夹名称", "폴더 이름 바꾸기 · 이동": "重命名 · 移动文件夹", diff --git a/lib/platform/android/android_shell.dart b/lib/platform/android/android_shell.dart index 091ce31..8f73793 100644 --- a/lib/platform/android/android_shell.dart +++ b/lib/platform/android/android_shell.dart @@ -52,6 +52,15 @@ class AndroidShell extends NoopShell { } catch (_) {} } + /// 미디어 목록 다시 읽기 (MainActivity "mediaScan" - MediaScannerConnection.scanFile). 실패해도 조용히 + @override + Future mediaScan(List paths) async { + if (paths.isEmpty) return; + try { + await _ch.invokeMethod('mediaScan', {'paths': paths}); + } catch (_) {} + } + /// 다른 앱으로 열기 (MainActivity "openWith"): [choose] 면 늘 앱 고르기 창, 아니면 기본 앱 (없으면 고르기 창) @override Future openWith(String path, {bool choose = false}) async { diff --git a/lib/platform/windows/desktop_shell.dart b/lib/platform/windows/desktop_shell.dart index fec6ed2..77fd6a1 100644 --- a/lib/platform/windows/desktop_shell.dart +++ b/lib/platform/windows/desktop_shell.dart @@ -376,6 +376,10 @@ class DesktopShell with WindowListener implements AppShell { @override Future shareFiles(List paths) async => false; + /// Windows 는 갤러리 미디어 목록이 없다 (.nomedia 는 Android 만) + @override + Future mediaScan(List paths) async {} + /// 기본 연결 프로그램 (탐색기에서 두 번 누른 것과 같음) 또는 "연결 프로그램" 고르기 창 @override Future openWith(String path, {bool choose = false}) async { diff --git a/lib/services/app_shell.dart b/lib/services/app_shell.dart index 73bcf18..dd368de 100644 --- a/lib/services/app_shell.dart +++ b/lib/services/app_shell.dart @@ -74,6 +74,10 @@ abstract class AppShell { bool get canShare; Future shareFiles(List paths); + /// X-plore 맞추기 (관리자 지시 10/20): 바뀐 파일 · 폴더를 기기의 미디어 목록에 다시 읽게 한다 (Android MediaScanner - + /// .nomedia 를 넣고 지운 뒤 갤러리에 반영). 다른 기기는 할 일 없음. 실패해도 조용히 넘긴다 + Future mediaScan(List paths); + /// 설치된 외부 브라우저 ('chrome', 'firefox', 'edge', 'whale' 중) List installedBrowsers(); @@ -147,6 +151,8 @@ class NoopShell implements AppShell { @override Future shareFiles(List paths) async => false; @override + Future mediaScan(List paths) async {} + @override List installedBrowsers() => const []; @override Future> missingTools() async => const []; diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index e7dbbb7..0e62a08 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -4102,7 +4102,7 @@ class _ExplorerPageState extends State { ? tr('이 폴더에 빈 .nomedia 파일을 만듭니다. 갤러리 · 음악 앱이 이 폴더 (안의 폴더 포함) 의 그림 · 동영상 · 음악을 보이지 않게 됩니다.') : tr('이 폴더의 .nomedia 파일을 지웁니다. 갤러리 · 음악 앱이 이 폴더의 그림 · 동영상 · 음악을 다시 보이게 됩니다.')), const SizedBox(height: 6), - Text(tr('폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.'), + Text(tr('폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.'), style: const TextStyle(fontSize: 12, color: JjColors.textDim)), ]), actions: [ @@ -4123,6 +4123,8 @@ class _ExplorerPageState extends State { _snack(trf('바꾸지 못했습니다: {0}', [err])); return; } + // 관리자 지시 10/20: 갤러리가 바로 알도록 .nomedia 와 그 폴더를 다시 읽게 (넣을 때 · 지울 때 모두, 실패해도 조용히) + await c.services.shell.mediaScan([f.path, dir]); await _refreshAll([dir]); pane.changed(); _snack(on ? tr('갤러리 · 음악 앱에서 숨겼습니다 (.nomedia). 파일은 그대로입니다.') : tr('.nomedia 를 지웠습니다. 갤러리 · 음악 앱에 다시 보입니다.')); diff --git a/test/explorer_test.dart b/test/explorer_test.dart index a5a2f87..96c149c 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -34,6 +34,9 @@ import 'package:jj_mkvmaker/ui/sync_preview_view.dart' show keepOptionsTogether; /// 다른 앱으로 열기를 기록만 하는 셸 (실제로 프로그램을 띄우지 않게) class _Shell extends NoopShell { final opened = <(String, bool)>[]; + final scanned = >[]; + @override + Future mediaScan(List paths) async => scanned.add(paths); @override Future openWith(String path, {bool choose = false}) async { opened.add((path, choose)); @@ -1767,8 +1770,11 @@ void main() { await act(tester, () => tester.tap(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'))); await calm(tester); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '숨기기'))); - await settle(tester, () => mark.existsSync()); + await settle(tester, () => mark.existsSync() && shell.scanned.isNotEmpty); expect(mark.lengthSync(), 0); + expect(shell.scanned, [ + [mark.path, p.join(left, 'sub')] + ], reason: '갤러리가 다시 읽게 (MediaScanner)'); expect(File(p.join(left, 'sub', 'inner.txt')).readAsStringSync(), 'inner', reason: '안의 파일은 그대로'); // 이제 메뉴는 [다시 보이기] await idle(tester); @@ -1780,6 +1786,8 @@ void main() { expect(find.textContaining('.nomedia 파일을 지웁니다'), findsOneWidget); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '다시 보이기'))); await settle(tester, () => !mark.existsSync()); + await settle(tester, () => shell.scanned.length == 2); + expect(shell.scanned.last, [mark.path, p.join(left, 'sub')], reason: '지울 때도 다시 읽게'); expect(File(p.join(left, 'sub', 'inner.txt')).existsSync(), isTrue); // 파일에는 없음 await idle(tester); From ba968cbed9c865ce161c0e14050448fa9c92b4b3 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:45:24 +0900 Subject: [PATCH 17/24] =?UTF-8?q?WIP=20X-plore=20=EB=B9=84=EA=B5=90=2040?= =?UTF-8?q?=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20):?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=ED=83=90=EC=83=89=EA=B8=B0=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EA=B8=80=EC=9E=90=20=ED=81=AC=EA=B8=B0=20(80~150%,?= =?UTF-8?q?=20=EC=B2=98=EC=9D=8C=20100%)=20=C2=B7=20=EC=A4=84=20=EA=B0=84?= =?UTF-8?q?=EA=B2=A9=20(=EC=A2=81=EA=B2=8C=200.8=20=C2=B7=20=EB=B3=B4?= =?UTF-8?q?=ED=86=B5=20=C2=B7=20=EB=84=93=EA=B2=8C=201.25,=20=EC=B2=98?= =?UTF-8?q?=EC=9D=8C=20=EB=B3=B4=ED=86=B5)=20-=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20>=20=ED=8C=8C=EC=9D=BC=20=ED=83=90?= =?UTF-8?q?=EC=83=89=EA=B8=B0,=20=EC=84=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20=EB=AA=A8=EB=91=90,=20=EC=95=B1=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=C2=B7=20=EA=B8=B0=EA=B8=B0=20=EA=B8=80?= =?UTF-8?q?=EC=9E=90=20=ED=81=AC=EA=B8=B0=EC=99=80=20=EA=B3=B1=ED=95=A8=20?= =?UTF-8?q?(=EC=B2=98=EC=9D=8C=20=EA=B0=92=EC=9D=80=20=EC=98=88=EC=A0=84?= =?UTF-8?q?=20=EC=A4=84=20=EB=86=92=EC=9D=B4=20=EA=B7=B8=EB=8C=80=EB=A1=9C?= =?UTF-8?q?)=20=C2=B7=20=EB=B2=84=EA=B7=B8=20=EA=B3=A0=EC=B9=A8:=20412=20?= =?UTF-8?q?=ED=8F=AD=20"=ED=8F=B4=EB=8D=94=20+=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D"=20=EC=9D=98=20=EC=A2=81=EC=9D=80=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=20(77px)=20=EC=97=90=EC=84=9C=20X-plore=20?= =?UTF-8?q?=C2=B7=20TC=20=EC=A4=84=EC=9D=B4=20=EB=84=98=EC=B9=98=EB=8D=98?= =?UTF-8?q?=20=EA=B2=83=20(100%=20=EC=97=90=EC=84=9C=EB=8F=84)=20-=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B9=B8=EC=9D=84=20=EB=82=A8?= =?UTF-8?q?=EC=9D=80=20=ED=8F=AD=EB=A7=8C=ED=81=BC=20=C2=B7=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20(=EC=84=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=C3=97?= =?UTF-8?q?=20100/150=20=EB=84=93=EA=B2=8C/=EA=B8=B0=EA=B8=B0=201.3=C3=971?= =?UTF-8?q?20=20=EC=A2=81=EA=B2=8C/80)=20=C2=B7=20=EC=BA=A1=EC=B2=98=20?= =?UTF-8?q?=EC=8B=9C=ED=97=98=20integration=5Ftest/explorer=5Ftext=5Fshot?= =?UTF-8?q?=5Ftest.dart=20(=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 --- assets/l10n/en.json | 8 ++ assets/l10n/ja.json | 8 ++ assets/l10n/zh-Hans.json | 8 ++ integration_test/explorer_text_shot_test.dart | 61 ++++++++++ lib/app/settings.dart | 15 +++ lib/ui/explorer_look.dart | 38 +++++- lib/ui/explorer_page.dart | 33 ++++-- lib/ui/settings_page.dart | 28 +++++ test/explorer_text_scale_test.dart | 110 ++++++++++++++++++ 9 files changed, 293 insertions(+), 16 deletions(-) create mode 100644 integration_test/explorer_text_shot_test.dart create mode 100644 test/explorer_text_scale_test.dart diff --git a/assets/l10n/en.json b/assets/l10n/en.json index c7fdc4d..83d6e0b 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -543,6 +543,7 @@ "{0} 페이지를 지울까요? (저장해야 파일에 반영됩니다)": "Delete page {0}? (Saved to the file only when you save)", "{0} 펼치기": "Expand {0}", "{0} 폴더가 없거나 열 수 없어 내역에서 뺐습니다": "Folder {0} is missing or can't be opened, so it was removed from history", + "{0}% (기본)": "{0}% (default)", "{0}% 에서 2분 넘게 나아가지 않아 멈췄습니다. 망이 Tor 를 막고 있거나 느릴 수 있습니다. 잠시 뒤 [다시 시도] 하세요.": "Stopped because it made no progress past {0}% for over 2 minutes. The network may be blocking Tor or be slow. Press [Try again] in a moment.", "{0}. {1} (대기)": "{0}. {1} (waiting)", "{0}: 이 동영상을 {1} 로 옮깁니다": "{0}: moves this video to {1}", @@ -927,6 +928,7 @@ "내장 저장소": "Internal storage", "내장 플레이어": "Built-in player", "내장 플레이어로 재생": "Play in built-in player", + "넓게": "Wide", "넘기는 방향: 오른쪽 → 왼쪽 (만화)": "Page direction: right → left (manga)", "넘기는 방향: 왼쪽 → 오른쪽": "Page direction: left → right", "넣으면 AES-256 으로 암호화합니다. 비우면 보통 ZIP": "If set, encrypts with AES-256. Leave empty for a normal ZIP", @@ -1257,6 +1259,7 @@ "목": "Thu", "목록": "list", "목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "Hides ad areas in lists · next to · on top of videos", + "목록 글자 크기": "List text size", "목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.": "You can see them again with [Show hidden items] at the bottom of the list, or in Settings > File explorer.", "목록 보기": "View list", "목록 파일은 전체 경로로 적어 주세요: {0}": "Please write the full path of the list file: {0}", @@ -1434,6 +1437,7 @@ "보조 DNS (비워 둘 수 있음)": "Secondary DNS (may be empty)", "보통": "Normal", "보통 (0.25, 기본)": "Normal (0.25, default)", + "보통 (기본)": "Normal (default)", "보통 비워 둡니다": "Usually left empty", "보통: {0}": "Normal: {0}", "복사": "Copy", @@ -2398,6 +2402,7 @@ "정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "Set. Asked when using saved passwords, settings or features that need a password (once until the app is closed)", "제목": "Title", "조정 전": "Before", + "좁게": "Compact", "좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)": "Height of the video list on narrow screens (Auto: as tall as its rows, keeping room for the details)", "종료": "Quit", "종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "When you press quit (close window ✕ · quit button)", @@ -2430,6 +2435,7 @@ "준비 중": "Preparing", "준비 중…": "Preparing…", "준비하는 중": "Preparing", + "줄 간격": "Row spacing", "줄 번호": "Line numbers", "줄 삭제": "Delete line", "중간에서 가로채는 공격일 수 있어 연결하지 않았습니다. 서버 인증서를 바꾼 것이 확실할 때만, 서버 설정의 [믿은 인증서 지우기] 뒤 다시 연결하세요.": "Not connected because this could be an interception attack. Only if you're sure the server certificate was changed, use [Forget trusted certificate] in the server settings and connect again.", @@ -2691,6 +2697,8 @@ "파일 탐색기 가운데에 [모니터링] 버튼: 복사 · 이동을 기억해 진행 · 대상 디스크 용량 · 옵션을 보여 주고, 옵션을 고쳐 다시 실행 · 재개하거나 lsync 로 옮깁니다 (복사 · rsync 와 lsync 를 따로).": "[Monitor] button in the middle of the file explorer: remembers copies · moves and shows progress · target disk space · options; change options to run again · resume, or move to lsync (copy · rsync and lsync shown separately).", "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer tap: single tap selects → single tap opens. To revert: Settings > File explorer > Tap", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "File explorer two-pane layout: follow screen shape → side by side. To revert: the [Columns ⇆ / Rows ⇅] button in the file explorer, or Pane layout", + "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "Only the name and size text in the file explorer list. Multiplied by the app screen size and the device text size", + "파일 탐색기 목록의 줄 높이": "Row height in the file explorer list", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "You can also change it from the ⋮ menu at the top of the file explorer (side by side · top and bottom · button bar position · buttons)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "File explorer panes: two panes → by screen size (one pane on a portrait phone). To revert: Settings > File explorer > Pane layout", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer: a single tap now opens a file right away (long-press to select or for the menu). To change: Settings > File explorer > Tap", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index a36da9e..876e41a 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -543,6 +543,7 @@ "{0} 페이지를 지울까요? (저장해야 파일에 반영됩니다)": "{0} ページを削除しますか? (保存するとファイルに反映されます)", "{0} 펼치기": "{0} を展開", "{0} 폴더가 없거나 열 수 없어 내역에서 뺐습니다": "{0} フォルダーが存在しないか開けないため、履歴から削除しました", + "{0}% (기본)": "{0}% (既定)", "{0}% 에서 2분 넘게 나아가지 않아 멈췄습니다. 망이 Tor 를 막고 있거나 느릴 수 있습니다. 잠시 뒤 [다시 시도] 하세요.": "{0}% から 2 分以上進まなかったため停止しました。ネットワークが Tor をブロックしているか、遅い可能性があります。少し待ってから [再試行] してください。", "{0}. {1} (대기)": "{0}. {1}(待機)", "{0}: 이 동영상을 {1} 로 옮깁니다": "{0}: この動画を {1} に移動します", @@ -927,6 +928,7 @@ "내장 저장소": "内部ストレージ", "내장 플레이어": "内蔵プレーヤー", "내장 플레이어로 재생": "内蔵プレーヤーで再生", + "넓게": "広く", "넘기는 방향: 오른쪽 → 왼쪽 (만화)": "めくる方向: 右 → 左 (マンガ)", "넘기는 방향: 왼쪽 → 오른쪽": "めくる方向: 左 → 右", "넣으면 AES-256 으로 암호화합니다. 비우면 보통 ZIP": "入力すると AES-256 で暗号化します。空欄なら通常の ZIP", @@ -1257,6 +1259,7 @@ "목": "木", "목록": "一覧", "목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "リスト・動画の横・動画の上に出る広告エリアを隠します", + "목록 글자 크기": "一覧の文字サイズ", "목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.": "リスト最下部の [隠した項目を表示] または 設定 > ファイルエクスプローラー で再び表示できます。", "목록 보기": "リストを見る", "목록 파일은 전체 경로로 적어 주세요: {0}": "リストファイルはフルパスで書いてください: {0}", @@ -1434,6 +1437,7 @@ "보조 DNS (비워 둘 수 있음)": "代替 DNS (空欄可)", "보통": "普通", "보통 (0.25, 기본)": "普通 (0.25、既定)", + "보통 (기본)": "標準 (既定)", "보통 비워 둡니다": "通常は空欄", "보통: {0}": "普通: {0}", "복사": "コピー", @@ -2398,6 +2402,7 @@ "정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "設定済み。保存したパスワード・設定・パスワードが必要な機能を使うときに尋ねます (アプリを終了するまで 1 回)", "제목": "タイトル", "조정 전": "調整前", + "좁게": "狭く", "좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)": "狭い画面で動画リストが占める高さ (自動: 行の数だけ、詳細欄の場所は残す)", "종료": "終了", "종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "終了(ウィンドウを閉じる ✕・終了ボタン)を押したとき", @@ -2430,6 +2435,7 @@ "준비 중": "準備中", "준비 중…": "準備中…", "준비하는 중": "準備中", + "줄 간격": "行の間隔", "줄 번호": "行番号", "줄 삭제": "行を削除", "중간에서 가로채는 공격일 수 있어 연결하지 않았습니다. 서버 인증서를 바꾼 것이 확실할 때만, 서버 설정의 [믿은 인증서 지우기] 뒤 다시 연결하세요.": "中間者攻撃の可能性があるため接続しませんでした。サーバー証明書を変えたことが確かな場合だけ、サーバー設定の [信頼した証明書を削除] の後に再接続してください。", @@ -2691,6 +2697,8 @@ "파일 탐색기 가운데에 [모니터링] 버튼: 복사 · 이동을 기억해 진행 · 대상 디스크 용량 · 옵션을 보여 주고, 옵션을 고쳐 다시 실행 · 재개하거나 lsync 로 옮깁니다 (복사 · rsync 와 lsync 를 따로).": "ファイルエクスプローラー中央に [モニタリング] ボタン: コピー · 移動を記憶して進捗 · 対象ディスク容量 · オプションを表示し、オプションを変えて再実行 · 再開、または lsync へ移動します (コピー · rsync と lsync は別々)。", "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラーのタップ: 1 回で選択 → 1 回ですぐ開く。元に戻す: 環境設定 > ファイルエクスプローラー > タップ", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "ファイルエクスプローラーの 2 画面配置: 画面の形に合わせる → 左右。元に戻す: ファイルエクスプローラーの [左右 ⇆ / 上下 ⇅] ボタンまたは画面配置", + "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "ファイルエクスプローラー一覧の名前・サイズの文字のみ。アプリの画面サイズ・端末の文字サイズと掛け合わされます", + "파일 탐색기 목록의 줄 높이": "ファイルエクスプローラー一覧の行の高さ", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "ファイルエクスプローラー上部の ⋮ メニューからも変えられます (左右 · 上下 · ボタン列の位置 · ボタン構成)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "ファイルエクスプローラーの画面: 2 画面 → 画面サイズに合わせる (縦向きのスマホは 1 画面)。元に戻す: 環境設定 > ファイルエクスプローラー > 画面配置", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラー: ファイルを 1 回タップするとすぐ開きます (選択 · メニューは長押し)。変更: 環境設定 > ファイルエクスプローラー > タップ", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 22d5464..5157828 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -543,6 +543,7 @@ "{0} 페이지를 지울까요? (저장해야 파일에 반영됩니다)": "删除第 {0} 页? (保存后才会写入文件)", "{0} 펼치기": "展开 {0}", "{0} 폴더가 없거나 열 수 없어 내역에서 뺐습니다": "文件夹 {0} 不存在或无法打开,已从历史记录中移除", + "{0}% (기본)": "{0}% (默认)", "{0}% 에서 2분 넘게 나아가지 않아 멈췄습니다. 망이 Tor 를 막고 있거나 느릴 수 있습니다. 잠시 뒤 [다시 시도] 하세요.": "在 {0}% 停滞超过 2 分钟,已停止。网络可能在阻止 Tor 或速度较慢。请稍后点 [重试]。", "{0}. {1} (대기)": "{0}. {1}(等待)", "{0}: 이 동영상을 {1} 로 옮깁니다": "{0}: 将此视频移到 {1}", @@ -927,6 +928,7 @@ "내장 저장소": "内部存储", "내장 플레이어": "内置播放器", "내장 플레이어로 재생": "用内置播放器播放", + "넓게": "宽松", "넘기는 방향: 오른쪽 → 왼쪽 (만화)": "翻页方向: 右 → 左 (漫画)", "넘기는 방향: 왼쪽 → 오른쪽": "翻页方向: 左 → 右", "넣으면 AES-256 으로 암호화합니다. 비우면 보통 ZIP": "填写后使用 AES-256 加密。留空则为普通 ZIP", @@ -1257,6 +1259,7 @@ "목": "四", "목록": "列表", "목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "隐藏列表 · 视频旁 · 视频上方出现的广告区域", + "목록 글자 크기": "列表文字大小", "목록 맨 아래의 [숨긴 항목 보이기] 나 환경 설정 > 파일 탐색기에서 다시 볼 수 있습니다.": "可通过列表底部的 [显示隐藏项] 或 设置 > 文件管理器 重新查看。", "목록 보기": "查看列表", "목록 파일은 전체 경로로 적어 주세요: {0}": "请填写列表文件的完整路径: {0}", @@ -1434,6 +1437,7 @@ "보조 DNS (비워 둘 수 있음)": "备用 DNS (可留空)", "보통": "普通", "보통 (0.25, 기본)": "普通 (0.25,默认)", + "보통 (기본)": "正常 (默认)", "보통 비워 둡니다": "通常留空", "보통: {0}": "普通:{0}", "복사": "复制", @@ -2398,6 +2402,7 @@ "정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "已设置。使用已保存的密码 · 设置 · 需要密码的功能时询问 (在关闭应用前只问一次)", "제목": "标题", "조정 전": "调整前", + "좁게": "紧凑", "좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)": "窄屏时视频列表所占高度 (自动: 按行数,并为详情区留出空间)", "종료": "退出", "종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "点击退出(关闭窗口 ✕ · 退出按钮)时", @@ -2430,6 +2435,7 @@ "준비 중": "准备中", "준비 중…": "准备中…", "준비하는 중": "正在准备", + "줄 간격": "行间距", "줄 번호": "行号", "줄 삭제": "删除行", "중간에서 가로채는 공격일 수 있어 연결하지 않았습니다. 서버 인증서를 바꾼 것이 확실할 때만, 서버 설정의 [믿은 인증서 지우기] 뒤 다시 연결하세요.": "可能是中间人攻击,因此未连接。仅当确定服务器证书已更换时,在服务器设置中 [删除已信任的证书] 后重新连接。", @@ -2691,6 +2697,8 @@ "파일 탐색기 가운데에 [모니터링] 버튼: 복사 · 이동을 기억해 진행 · 대상 디스크 용량 · 옵션을 보여 주고, 옵션을 고쳐 다시 실행 · 재개하거나 lsync 로 옮깁니다 (복사 · rsync 와 lsync 를 따로).": "文件浏览器中间的 [监控] 按钮: 记住复制 · 移动并显示进度 · 目标磁盘容量 · 选项,可修改选项后重新运行 · 继续,或移到 lsync (复制 · rsync 与 lsync 分开显示)。", "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器点击: 单击选择 → 单击直接打开。恢复: 设置 > 文件浏览器 > 点击", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "文件浏览器双窗格布局: 跟随屏幕形状 → 左右。恢复: 文件浏览器中的 [左右 ⇆ / 上下 ⇅] 按钮或窗格布局", + "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "仅文件管理器列表中的名称和大小文字。会与应用界面大小及设备文字大小相乘", + "파일 탐색기 목록의 줄 높이": "文件管理器列表的行高", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "也可以在文件浏览器顶部的 ⋮ 菜单中更改 (左右 · 上下 · 按钮栏位置 · 按钮配置)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "文件浏览器窗格: 双窗格 → 按屏幕大小 (竖屏手机为单窗格)。恢复: 设置 > 文件浏览器 > 窗格布局", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器:单击文件会立即打开(选择 · 菜单请长按)。更改:设置 > 文件浏览器 > 点按", diff --git a/integration_test/explorer_text_shot_test.dart b/integration_test/explorer_text_shot_test.dart new file mode 100644 index 0000000..cb4edd9 --- /dev/null +++ b/integration_test/explorer_text_shot_test.dart @@ -0,0 +1,61 @@ +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/services/platform_services.dart'; +import 'package:jj_mkvmaker/ui/explorer_page.dart'; +import 'package:jj_mkvmaker/ui/theme.dart'; +import 'package:path/path.dart' as p; + +/// X-plore 비교 40 (관리자 지시 10/20): 412 폭 · 목록 글자 150% · 넓게 (그리고 처음 값) 를 실제 글꼴로 그려 PNG 로 (JJ_SHOT_DIR). +/// 세 스타일 모두. 넘치면 (RenderFlex overflow) 시험이 실패한다 +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('40: 목록 글자 크기 · 줄 간격 화면 그림 (412 폭)', (tester) async { + final out = Platform.environment['JJ_SHOT_DIR'] ?? Directory.systemTemp.path; + final tmp = Directory.systemTemp.createTempSync('jj_textshot_'); + addTearDown(() => tmp.deleteSync(recursive: true)); + File(p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 글자 크기 시험용 2026 최종본.mp4')).writeAsStringSync('v' * 123456); + File(p.join(tmp.path, 'Very long English file name for the text size check.txt')).writeAsStringSync('t' * 4321); + File(p.join(tmp.path, 'a.jpg')).writeAsStringSync('x'); + Directory(p.join(tmp.path, '폴더 하나')).createSync(); + Directory(p.join(tmp.path, 'Another folder with a long name')).createSync(); + final c = AppController(PlatformServices.create()); + c.settings.explorerPaths = [tmp.path, tmp.path]; + final key = GlobalKey(); + for (final layout in ['single', 'split']) { + for (final style in ['xplore', 'windows', 'totalcmd']) { + for (final (scale, spacing) in [(100, 'normal'), (150, 'wide'), (80, 'compact')]) { + c.settings + ..explorerLayout = layout + ..explorerStyle = style + ..explorerTextScale = scale + ..explorerRowSpacing = spacing; + tester.view.physicalSize = const Size(412, 900); + tester.view.devicePixelRatio = 1; + await tester.runAsync(() => tester.pumpWidget(MaterialApp( + theme: buildTheme(), + home: RepaintBoundary(key: key, child: ExplorerPage(c: c)), + ))); + for (var i = 0; i < 12; i++) { + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 250))); + await tester.pump(); + } + expect(tester.takeException(), isNull, reason: '$layout $style $scale% $spacing 넘치지 않음'); + final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary; + final image = await tester.runAsync(() => boundary.toImage()); + final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png)); + File('$out/explorer_text_${layout}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List()); + await tester.pumpWidget(const SizedBox()); + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 300))); + } + } + } + tester.view.reset(); + }); +} diff --git a/lib/app/settings.dart b/lib/app/settings.dart index 1b828e4..e2ae8f1 100644 --- a/lib/app/settings.dart +++ b/lib/app/settings.dart @@ -418,6 +418,14 @@ class AppSettings { /// 다시 · 두 번 눌러도 아래 줄이 움직이지 않게) 와 부딪혀 설정으로만. 켜도 두 번 누르기의 두 번째는 접지 않는다 bool explorerRetapCollapse = false; + /// X-plore 비교 40 (관리자 지시 10/20): 파일 탐색기 목록만의 글자 크기 (%, 80~150, 처음 100 = 지금 그대로). + /// 앱 화면 크기 · 기기 글자 크기와 곱해진다 + int explorerTextScale = 100; + static const explorerTextScaleMin = 80, explorerTextScaleMax = 150; + + /// X-plore 비교 40: 목록 줄 간격 'compact' 좁게 · 'normal' 보통 (처음 값 = 지금 그대로) · 'wide' 넓게 + String explorerRowSpacing = 'normal'; + /// X-plore 비교 i: 일괄 이름 바꾸기의 마지막 규칙 (다음에 창이 그 값으로 열림, 비면 처음 값) Map batchRenameLast = {}; @@ -832,6 +840,8 @@ class AppSettings { 'explorerLayout': explorerLayout, 'explorerOnePaneCopy': explorerOnePaneCopy, 'explorerRetapCollapse': explorerRetapCollapse, + 'explorerTextScale': explorerTextScale, + 'explorerRowSpacing': explorerRowSpacing, 'batchRenameLast': batchRenameLast, 'explorerRootsInTree': explorerRootsInTree, 'explorerAlwaysMarks': explorerAlwaysMarks, @@ -1063,6 +1073,11 @@ class AppSettings { // 'auto' = 넓은 화면은 두 창, 좁은 화면 (폰 세로) 은 한 창. 예전 기본 'dual' 은 한 번 'auto' 로 ..explorerOnePaneCopy = j['explorerOnePaneCopy'] == 'other' ? 'other' : 'clip' ..explorerRetapCollapse = j['explorerRetapCollapse'] == true + // 값이 없거나 틀리면 지금 그대로 (100% · 보통), 범위 밖은 끝으로 + ..explorerTextScale = j['explorerTextScale'] is num + ? (j['explorerTextScale'] as num).round().clamp(explorerTextScaleMin, explorerTextScaleMax) + : 100 + ..explorerRowSpacing = const {'compact', 'normal', 'wide'}.contains(j['explorerRowSpacing']) ? j['explorerRowSpacing'] as String : 'normal' ..batchRenameLast = j['batchRenameLast'] is Map ? Map.from(j['batchRenameLast'] as Map) : {} ..explorerRootsInTree = j['explorerRootsInTree'] != false ..explorerAlwaysMarks = j['explorerAlwaysMarks'] != false diff --git a/lib/ui/explorer_look.dart b/lib/ui/explorer_look.dart index 6259118..525c0dc 100644 --- a/lib/ui/explorer_look.dart +++ b/lib/ui/explorer_look.dart @@ -22,10 +22,11 @@ enum ExplorerStyle { double get rowHeight => switch (this) { xplore => 52, windows => 34, totalcmd => 26 }; - /// 253: 줄 높이 - 기기 글자 크기를 키우면 그만큼 (고정 높이라 1.6배에서 이름 · 크기 두 줄이 넘쳤다) - double rowHeightOf(BuildContext context) { - final s = MediaQuery.textScalerOf(context).scale(fontSize) / fontSize; - return s <= 1 ? rowHeight : (rowHeight * s).roundToDouble(); + /// 253: 줄 높이 - 기기 글자 크기를 키우면 그만큼 (고정 높이라 1.6배에서 이름 · 크기 두 줄이 넘쳤다). + /// X-plore 비교 40: [textScale] 목록 글자 크기 (기기 글자 크기와 곱함) · [spacing] 줄 간격 ([rowSpacingFactor]) + double rowHeightOf(BuildContext context, {double textScale = 1, double spacing = 1}) { + final s = MediaQuery.textScalerOf(context).scale(fontSize) * textScale / fontSize; + return ((s <= 1 ? rowHeight : rowHeight * s) * spacing).roundToDouble(); } double get iconSize => switch (this) { xplore => 30, windows => 20, totalcmd => 16 }; double get indent => switch (this) { xplore => 16, windows => 14, totalcmd => 12 }; @@ -48,6 +49,35 @@ const _sheets = {'xls', 'xlsx', 'ods'}; const _slides = {'ppt', 'pptx', 'odp'}; const _programs = {'exe', 'msi', 'bat', 'cmd', 'ps1', 'apk'}; +/// X-plore 비교 40: 줄 간격 설정 → 줄 높이 배수 (좁게 · 보통 (지금 그대로) · 넓게) +double rowSpacingFactor(String spacing) => switch (spacing) { 'compact' => 0.8, 'wide' => 1.25, _ => 1.0 }; + +/// X-plore 비교 40: 목록 글자만 [factor] 배 (앱 화면 크기 · 기기 글자 크기에 곱함). 1 이면 그대로 +Widget scaleListText(BuildContext context, double factor, Widget child) { + if (factor == 1) return child; + final mq = MediaQuery.of(context); + return MediaQuery(data: mq.copyWith(textScaler: _TimesScaler(mq.textScaler, factor)), child: child); +} + +class _TimesScaler extends TextScaler { + final TextScaler base; + final double factor; + const _TimesScaler(this.base, this.factor); + + @override + double scale(double fontSize) => base.scale(fontSize) * factor; + + // ignore: deprecated_member_use + @override + double get textScaleFactor => base.scale(14) / 14 * factor; + + @override + bool operator ==(Object other) => other is _TimesScaler && other.base == base && other.factor == factor; + + @override + int get hashCode => Object.hash(base, factor); +} + /// 파일 종류별 아이콘 · 색 (스타일마다 다르게) (IconData, Color) fileIcon(ExplorerStyle style, FileEntry e, {bool open = false}) { final x = e.ext; diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 0e62a08..2400dbf 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:io'; +import 'dart:math' as math; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart' show defaultTargetPlatform; @@ -1076,7 +1077,7 @@ class _ExplorerPageState extends State { final pos = pane.scroll.position; // 180: 끝 (maxScrollExtent) 으로 자르지 않는다 - 줄이 막 늘어난 때는 끝이 아직 앞의 것이라 보던 폴더로 못 간다. // 넘치면 다음 배치 때 실제 끝으로 맞춰진다 - pane.scroll.jumpTo((i * _look.rowHeightOf(context) - pos.viewportDimension / 4).clamp(0.0, double.infinity)); + pane.scroll.jumpTo((i * _rowH() - pos.viewportDimension / 4).clamp(0.0, double.infinity)); } /// 28 확인 (10/20): 창의 목록이 화면에 새로 붙으면 (좁은 Rsync 화면의 [오른쪽] 탭을 처음 볼 때 · 폭이 바뀌어 두 창이 될 때) @@ -1145,6 +1146,10 @@ class _ExplorerPageState extends State { /// 지금 모양 (환경 설정 > 파일 탐색기 > 스타일) ExplorerStyle get _look => ExplorerStyle.of(c.settings.explorerStyle); + /// X-plore 비교 40: 목록 글자 크기 (설정 %) · 줄 간격을 넣은 줄 높이 + double get _listTextScale => c.settings.explorerTextScale / 100; + double _rowH() => _look.rowHeightOf(context, textScale: _listTextScale, spacing: rowSpacingFactor(c.settings.explorerRowSpacing)); + /// 왼쪽 폴더 트리 + 오른쪽 파일 목록 배치 bool get _split => !widget.rsync && c.settings.explorerLayout == 'split'; @@ -4916,16 +4921,16 @@ class _ExplorerPageState extends State { ), Expanded( child: Stack(children: [ - ListView.builder( + scaleListText(context, _listTextScale, ListView.builder( controller: pane.scroll, itemCount: rows.length, - itemExtent: look.rowHeightOf(context), + itemExtent: _rowH(), // 76: 줄 위젯은 항목을 따라간다 (아래 [_rowKey]) itemBuilder: (_, k) => KeyedSubtree( key: _rowKey(rows[k]), child: rows[k].kind != 0 ? _extraRow(pane, rows[k]) : _rowView(pane, rows[k], compact: foldersOnly)), findChildIndexCallback: _rowIndexOf(rows), - ), + )), // 읽는 중 (WebDAV 서버를 기다릴 때 등). 180: 목록 위에 겹쳐 그린다 (끼어들면 목록이 밀려 다음 것을 누르려다 엉뚱한 것을 누른다) if (pane.loading.isNotEmpty) const Positioned(left: 0, right: 0, top: 0, child: LinearProgressIndicator(minHeight: 2)), ]), @@ -4985,14 +4990,14 @@ class _ExplorerPageState extends State { ? const Center(child: CircularProgressIndicator()) : entries.isEmpty && !up ? Center(child: Text(tr('비어 있음'), style: const TextStyle(color: JjColors.textDim))) - : ListView.builder( + : scaleListText(context, _listTextScale, ListView.builder( controller: pane.listScroll, itemCount: rows.length, - itemExtent: look.rowHeightOf(context), + itemExtent: _rowH(), itemBuilder: (_, k) => KeyedSubtree( key: _rowKey(rows[k]), child: rows[k].kind != 0 ? _extraRow(pane, rows[k]) : _rowView(pane, rows[k], list: true)), findChildIndexCallback: _rowIndexOf(rows), - ), + )), ), if (_selecting) _marksBar(pane, listDir: dir), ])); @@ -5495,7 +5500,7 @@ class _ExplorerPageState extends State { // 손가락으로 누르는 기기 (Android) 는 폭도 넓게 Widget chevron() => SizedBox( width: chevronWidth, - height: look.rowHeightOf(context), + height: _rowH(), child: (e.isDir || _canBrowseArchive(e)) && !list && !row.isUp ? (loading ? const Center( @@ -5521,15 +5526,19 @@ class _ExplorerPageState extends State { Widget nameCell() => LayoutBuilder(builder: (context, box) { final fixed = (list ? 0 : chevronWidth) + (look.rich ? 64 : look.iconSize + 12) + 48.0; final indent = list ? 0.0 : (row.depth * look.indent).clamp(0.0, (box.maxWidth - fixed).clamp(0.0, double.infinity)); + // 아주 좁은 창 (412 폭 "폴더 + 파일 목록" 의 트리, 77px 쯤): 아이콘 칸 · 틈을 남은 폭만큼만 (아이콘은 줄여 그림) - 넘치지 않게 + final room = (box.maxWidth - indent - (list ? 0 : chevronWidth)).clamp(0.0, double.infinity); + final iconW = math.min(look.rich ? 56.0 : look.iconSize + 6, room); + final gapW = math.min(look.rich ? 8.0 : 6.0, room - iconW); return Row(children: [ SizedBox(width: indent), if (!list) chevron(), SizedBox( - width: look.rich ? 56 : look.iconSize + 6, - height: look.rowHeightOf(context) - 8, - child: Center(child: _icon(e, row.isRoot, open, up: row.isUp)), + width: iconW, + height: _rowH() - 8, + child: Center(child: FittedBox(fit: BoxFit.scaleDown, child: _icon(e, row.isRoot, open, up: row.isUp))), ), - SizedBox(width: look.rich ? 8 : 6), + SizedBox(width: gapW), Expanded( child: look.rich && !e.isDir && !row.isUp ? Column( diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index fb538df..7778aa0 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -896,6 +896,34 @@ class _SettingsPageState extends State { value: s.explorerRetapCollapse, onChanged: (v) => c.updateSettings((x) => x.explorerRetapCollapse = v), ), + // X-plore 비교 40 (관리자 지시 10/20): 목록만의 글자 크기 · 줄 간격 (세 스타일 모두, 앱 화면 크기 · 기기 글자 크기와 곱함) + SettingTile( + title: Text(tr('목록 글자 크기')), + subtitle: Text(tr('파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다')), + trailing: DropdownButton( + value: s.explorerTextScale, + items: [ + for (var v = AppSettings.explorerTextScaleMin; v <= AppSettings.explorerTextScaleMax; v += 10) + DropdownMenuItem(value: v, child: Text(v == 100 ? trf('{0}% (기본)', [v]) : '$v%')), + if (s.explorerTextScale % 10 != 0) + DropdownMenuItem(value: s.explorerTextScale, child: Text('${s.explorerTextScale}%')), + ], + onChanged: (v) => c.updateSettings((x) => x.explorerTextScale = v!), + ), + ), + SettingTile( + title: Text(tr('줄 간격')), + subtitle: Text(tr('파일 탐색기 목록의 줄 높이')), + trailing: DropdownButton( + value: s.explorerRowSpacing, + items: [ + DropdownMenuItem(value: 'compact', child: Text(tr('좁게'))), + DropdownMenuItem(value: 'normal', child: Text(tr('보통 (기본)'))), + DropdownMenuItem(value: 'wide', child: Text(tr('넓게'))), + ], + onChanged: (v) => c.updateSettings((x) => x.explorerRowSpacing = v!), + ), + ), // X-plore 숨기기: 목록에서 숨긴 것 보기 · 풀기 (관리자 결정 10/18) ListTile( title: Text(trf('목록에서 숨긴 항목 ({0}개)', [s.explorerHiddenItems.length])), diff --git a/test/explorer_text_scale_test.dart b/test/explorer_text_scale_test.dart new file mode 100644 index 0000000..2c2a70f --- /dev/null +++ b/test/explorer_text_scale_test.dart @@ -0,0 +1,110 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/app/settings.dart'; +import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; +import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; +import 'package:jj_mkvmaker/services/platform_services.dart'; +import 'package:jj_mkvmaker/ui/explorer_page.dart'; +import 'package:path/path.dart' as p; + +/// X-plore 비교 40 (관리자 지시 10/20): 파일 탐색기 목록만의 글자 크기 (80~150%) · 줄 간격 (좁게 · 보통 · 넓게) +void main() { + late Directory tmp; + setUp(() => tmp = Directory.systemTemp.createTempSync('jj_textscale_')); + tearDown(() => tmp.deleteSync(recursive: true)); + + test('설정: 처음은 100% · 보통 (지금 그대로), 저장 · 읽기, 틀린 값은 그대로 쪽으로 · 범위 밖은 끝으로', () { + final s = AppSettings(); + expect((s.explorerTextScale, s.explorerRowSpacing), (100, 'normal')); + s + ..explorerTextScale = 130 + ..explorerRowSpacing = 'wide'; + final back = AppSettings.fromJson(s.toJson()); + expect((back.explorerTextScale, back.explorerRowSpacing), (130, 'wide')); + expect(AppSettings.fromJson({}).explorerTextScale, 100); + expect(AppSettings.fromJson({'explorerTextScale': 'x', 'explorerRowSpacing': 'huge'}).explorerRowSpacing, 'normal'); + expect(AppSettings.fromJson({'explorerTextScale': 300}).explorerTextScale, 150); + expect(AppSettings.fromJson({'explorerTextScale': 10}).explorerTextScale, 80); + }); + + /// [name] 글자의 실제 글자 크기 (기기 · 목록 글자 크기를 곱한 것) + double shownSize(WidgetTester tester, String name) { + final para = tester.renderObject(find.text(name).last); + final size = para.text.style?.fontSize ?? 14; + return para.textScaler.scale(size); + } + + /// 이름 줄 높이 + double rowHeight(WidgetTester tester, String name) => + tester.getSize(find.ancestor(of: find.text(name).last, matching: find.byType(KeyedSubtree)).first).height; + + Future open(WidgetTester tester, String style, String wait, + {double device = 1, int scale = 100, String spacing = 'normal'}) async { + const pp = MethodChannel('plugins.flutter.io/path_provider'); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, (_) async => tmp.path); + addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(pp, null)); + File(p.join(tmp.path, '아주 긴 이름의 동영상 파일 - 글자 크기 시험용 2026 최종본.mp4')).writeAsStringSync('v' * 2000); + File(p.join(tmp.path, 'a.txt')).writeAsStringSync('a'); + Directory(p.join(tmp.path, 'sub')).createSync(); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings + ..explorerPaths = [tmp.path, tmp.path] + // 폴더 + 파일 목록: 오른쪽 목록은 그 폴더의 것만이라 짧다 (트리는 Temp 의 다른 폴더가 많아 아래로 밀린다) + ..explorerLayout = 'split' + ..explorerStyle = style + ..explorerTextScale = scale + ..explorerRowSpacing = spacing; + await tester.runAsync(() => tester.pumpWidget(MaterialApp( + builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: TextScaler.linear(device)), child: child!), + home: ExplorerPage(c: c)))); + // 목록을 다 읽을 때까지 (다른 시험과 함께 돌면 느리다 - 10초까지) + for (var i = 0; i < 200 && (i < 15 || find.text(wait).evaluate().isEmpty); i++) { + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 50))); + await tester.pump(); + } + return c; + } + + Future close(WidgetTester tester) async { + await tester.pumpWidget(const SizedBox()); + await tester.pump(const Duration(seconds: 1)); + } + + for (final style in ['xplore', 'windows', 'totalcmd']) { + final a = style == 'totalcmd' ? 'a' : 'a.txt'; + testWidgets('40: $style - 처음 값 (100% · 보통) 은 지금 그대로 · 150% · 넓게는 글자 1.5배 · 줄 높이 늘어남 · 412 폭에서 넘치지 않음', (tester) async { + tester.view.physicalSize = const Size(412, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await open(tester, style, a); + expect(tester.takeException(), isNull); + final base = shownSize(tester, a), baseH = rowHeight(tester, a); + expect(baseH, {'xplore': 52.0, 'windows': 34.0, 'totalcmd': 26.0}[style], reason: '처음 값은 예전 줄 높이 그대로'); + await close(tester); + + await open(tester, style, a, scale: 150, spacing: 'wide'); + expect(tester.takeException(), isNull, reason: '412 폭 · 150% · 넓게에서 넘치지 않는다'); + expect(shownSize(tester, a), closeTo(base * 1.5, 0.01)); + expect(rowHeight(tester, a), (baseH * 1.5 * 1.25).roundToDouble()); + await close(tester); + + // 기기 글자 크기 1.3 과 곱해진다 · 좁게 + await open(tester, style, a, device: 1.3, scale: 120, spacing: 'compact'); + expect(tester.takeException(), isNull); + expect(shownSize(tester, a), closeTo(base * 1.3 * 1.2, 0.01)); + expect(rowHeight(tester, a), (baseH * 1.3 * 1.2 * 0.8).roundToDouble()); + await close(tester); + + // 80% 는 글자만 작게 (줄은 예전 높이 - 누르기 크기를 줄이지 않음) + await open(tester, style, a, scale: 80); + expect(shownSize(tester, a), closeTo(base * 0.8, 0.01)); + expect(rowHeight(tester, a), baseH); + await close(tester); + }); + } +} From 9ff834383551839a6efe7b6f82c8292922672d0a Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:39:31 +0900 Subject: [PATCH 18/24] =?UTF-8?q?WIP=20=EC=A2=81=EC=9D=80=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20"=ED=8F=B4=EB=8D=94=20+=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D"=20=ED=8A=B8=EB=A6=AC=20=EC=B0=BD=20(?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20):=20?= =?UTF-8?q?=ED=8F=AD=20<=20600=20=EC=9D=B4=EB=A9=B4=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EC=97=90=20=EC=B5=9C=EC=86=8C=20=ED=8F=AD=20(130px=20=EB=98=90?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=ED=8A=BC=20=EC=A4=84=EC=9D=84=20=EB=BA=80?= =?UTF-8?q?=20=ED=8F=AD=EC=9D=98=2042%)=20=C2=B7=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EC=97=90=EB=8A=94=20=E2=9C=94=20=EC=B9=B8=20=C2=B7=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0=20=EC=A4=84=20=EC=97=86=EC=9D=B4=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EB=A7=8C=20(=EC=84=A0=ED=83=9D=EC=9D=80=20=EC=98=A4=EB=A5=B8?= =?UTF-8?q?=EC=AA=BD=20=EB=AA=A9=EB=A1=9D),=20=EC=A4=84=EC=97=90=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=80=EB=A9=B4=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EB=91=90=20=EC=A4=84=EA=B9=8C=EC=A7=80=20=C2=B7=20?= =?UTF-8?q?=EC=9E=91=EC=9D=80=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B9=B8=20?= =?UTF-8?q?(28)=20=C2=B7=20=ED=99=94=EC=82=B4=ED=91=9C=2036=20=C2=B7=20?= =?UTF-8?q?=EB=93=A4=EC=97=AC=EC=93=B0=EA=B8=B0=20=EB=B0=98=20(=ED=8F=AD?= =?UTF-8?q?=EC=9D=98=201/10=20=EA=B9=8C=EC=A7=80)=20-=20412=20=ED=8F=AD?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=8A=B8=EB=A6=AC=2077px=20=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=B4=20=ED=95=9C=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=EB=8F=84=20=EC=95=88=20=EB=B3=B4=EC=9D=B4=EB=8D=98=20=EA=B2=83?= =?UTF-8?q?=20=C2=B7=20=EB=84=93=EC=9D=80=20=ED=99=94=EB=A9=B4=EC=9D=80=20?= =?UTF-8?q?=EA=B7=B8=EB=8C=80=EB=A1=9C=20(2=20:=203,=20=E2=9C=94)=20=C2=B7?= =?UTF-8?q?=20=EB=81=8C=EC=96=B4=EC=84=9C=20=ED=8F=AD=20=EB=B0=94=EA=BE=B8?= =?UTF-8?q?=EA=B8=B0=EB=8A=94=20=EC=A7=80=EA=B8=88=20=EC=97=86=EC=9D=8C=20?= =?UTF-8?q?=C2=B7=20=EC=8B=9C=ED=97=98=20(412=20=C2=B7=20360=20=C3=97=203?= =?UTF-8?q?=20=EC=8A=A4=ED=83=80=EC=9D=BC,=20Android=20=ED=99=94=EC=82=B4?= =?UTF-8?q?=ED=91=9C=20=ED=8F=AD)=20=C2=B7=20=EC=BA=A1=EC=B2=98=EB=8A=94?= =?UTF-8?q?=20Android=20=ED=94=8C=EB=9E=AB=ED=8F=BC=EC=9C=BC=EB=A1=9C=20(W?= =?UTF-8?q?indows=20=EB=A1=9C=20=EA=B7=B8=EB=A6=AC=EB=A9=B4=20=ED=99=94?= =?UTF-8?q?=EC=82=B4=ED=91=9C=EA=B0=80=2020=20=EC=9D=B4=EB=9D=BC=20?= =?UTF-8?q?=ED=8F=B0=EB=B3=B4=EB=8B=A4=20=EB=84=89=EB=84=89=ED=95=B4=20?= =?UTF-8?q?=EB=B3=B4=EC=98=80=EB=8B=A4)=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=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 --- integration_test/explorer_text_shot_test.dart | 12 +++-- lib/ui/explorer_page.dart | 44 +++++++++++++++---- test/explorer_text_scale_test.dart | 40 +++++++++++++++++ 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/integration_test/explorer_text_shot_test.dart b/integration_test/explorer_text_shot_test.dart index cb4edd9..20a208c 100644 --- a/integration_test/explorer_text_shot_test.dart +++ b/integration_test/explorer_text_shot_test.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -28,7 +29,9 @@ void main() { final c = AppController(PlatformServices.create()); c.settings.explorerPaths = [tmp.path, tmp.path]; final key = GlobalKey(); - for (final layout in ['single', 'split']) { + // 폰처럼 (Android 는 트리 펼치기 화살표가 44 로 넓다 - Windows 로 그리면 폰보다 넉넉해 보인다) + debugDefaultTargetPlatformOverride = TargetPlatform.android; + for (final (layout, width) in [('single', 412.0), ('split', 412.0), ('split', 360.0)]) { for (final style in ['xplore', 'windows', 'totalcmd']) { for (final (scale, spacing) in [(100, 'normal'), (150, 'wide'), (80, 'compact')]) { c.settings @@ -36,7 +39,7 @@ void main() { ..explorerStyle = style ..explorerTextScale = scale ..explorerRowSpacing = spacing; - tester.view.physicalSize = const Size(412, 900); + tester.view.physicalSize = Size(width, 900); tester.view.devicePixelRatio = 1; await tester.runAsync(() => tester.pumpWidget(MaterialApp( theme: buildTheme(), @@ -46,16 +49,17 @@ void main() { await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 250))); await tester.pump(); } - expect(tester.takeException(), isNull, reason: '$layout $style $scale% $spacing 넘치지 않음'); + expect(tester.takeException(), isNull, reason: '$layout $width $style $scale% $spacing 넘치지 않음'); final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary; final image = await tester.runAsync(() => boundary.toImage()); final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png)); - File('$out/explorer_text_${layout}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List()); + File('$out/explorer_text_$layout${width.round()}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List()); await tester.pumpWidget(const SizedBox()); await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 300))); } } } + debugDefaultTargetPlatformOverride = null; tester.view.reset(); }); } diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index 2400dbf..ecac2a9 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -1153,6 +1153,11 @@ class _ExplorerPageState extends State { /// 왼쪽 폴더 트리 + 오른쪽 파일 목록 배치 bool get _split => !widget.rsync && c.settings.explorerLayout == 'split'; + /// 관리자 지시 10/20: 좁은 화면 (폭 < [narrowSplitWidth]) 의 "폴더 + 파일 목록" 좌우 배치 - 트리 창에 최소 폭을 두고 + /// ✔ 칸 · 크기 줄을 숨긴다 (412 폭에서 트리가 77px 이라 폴더 이름이 한 글자도 안 보였다). [_body] 가 정한다 + bool _narrowSplit = false; + static const narrowSplitWidth = 600.0; + /// 좌우 두 창 (Rsync 화면은 늘) bool get _dual => widget.rsync || @@ -4589,6 +4594,7 @@ class _ExplorerPageState extends State { // 191: Rsync 화면이 좁으면 (폴드 접은 화면 · 폰 세로의 좌우 두 창) 창 하나씩 - [왼쪽 | 오른쪽] 으로 바꿔 본다 // (두 창 + 가운데 버튼 줄이면 창이 140px 남짓이라 폴더 이름이 잘렸다) final rsyncOne = widget.rsync && side && box.maxWidth < rsyncTwoPaneMinWidth; + _narrowSplit = split && side && box.maxWidth < narrowSplitWidth; final bar = s.explorerToolbar == 'hidden' && !widget.rsync ? null : _Toolbar( @@ -4627,8 +4633,14 @@ class _ExplorerPageState extends State { Expanded(child: keep(1, _paneView(1, foldersOnly: widget.rsync))), if (edge) bar, ] else if (split) ...[ - // 왼쪽 폴더 트리 (좁게) · 오른쪽 그 폴더의 파일 목록 - Expanded(flex: 2, child: keep(0, _paneView(0, foldersOnly: true))), + // 왼쪽 폴더 트리 (좁게) · 오른쪽 그 폴더의 파일 목록. 좁은 화면은 트리에 최소 폭 (130px 또는 버튼 줄 (72) 을 뺀 폭의 42%) - + // 버튼 줄까지 넣어 38% 로 하면 360 폭에서 오른쪽 목록의 파일 이름이 두세 글자만 남았다 + if (_narrowSplit) + SizedBox( + width: math.max(130.0, (box.maxWidth - (middle || edge ? 72 : 0)) * 0.42), + child: keep(0, _paneView(0, foldersOnly: true))) + else + Expanded(flex: 2, child: keep(0, _paneView(0, foldersOnly: true))), if (middle) bar, Expanded(flex: 3, child: keep(2, _listView(_panes[0]))), if (edge) bar, @@ -5476,6 +5488,9 @@ class _ExplorerPageState extends State { Widget _rowView(_Pane pane, _Row row, {bool list = false, bool compact = false}) { final e = row.entry; final look = _look; + // 좁은 "폴더 + 파일 목록" 의 트리 (관리자 지시 10/20): ✔ 칸 · 크기 줄 없이 이름만, 줄에 두 줄이 들어가면 이름을 두 줄까지 + final slim = compact && _narrowSplit && !list; + final nameLines = slim && _rowH() >= MediaQuery.textScalerOf(context).scale(look.fontSize) * _listTextScale * 2.5 + 6 ? 2 : 1; final isCurrent = e.isDir && !list && !row.isUp && samePath(pane.current, e.path); final isFocused = !row.isUp && pane.focused != null && samePath(pane.focused!, e.path); final marked = _isMarked(pane, e.path); @@ -5495,7 +5510,8 @@ class _ExplorerPageState extends State { color: isCurrent ? JjColors.accent : null, ); - final chevronWidth = treeChevronWidth(defaultTargetPlatform); + // 좁은 트리 (slim) 는 조금 좁게 (36 - 줄 높이 전체를 누르므로 손가락으로도 눌림) + final chevronWidth = slim ? math.min(36.0, treeChevronWidth(defaultTargetPlatform)) : treeChevronWidth(defaultTargetPlatform); // 트리의 펼치기 화살표 (따로 누름). 189: 접기 · 펼치기는 이것으로만 하므로 줄 높이 전체를 누를 수 있게, // 손가락으로 누르는 기기 (Android) 는 폭도 넓게 Widget chevron() => SizedBox( @@ -5525,11 +5541,17 @@ class _ExplorerPageState extends State { // 좁은 창 (폰 세로의 좌우 두 창) 에서 깊은 폴더: 들여쓰기는 이름 자리를 남기는 만큼만 (넘치지 않게) Widget nameCell() => LayoutBuilder(builder: (context, box) { final fixed = (list ? 0 : chevronWidth) + (look.rich ? 64 : look.iconSize + 12) + 48.0; - final indent = list ? 0.0 : (row.depth * look.indent).clamp(0.0, (box.maxWidth - fixed).clamp(0.0, double.infinity)); + // 좁은 트리 (slim): 들여쓰기는 반만 (폭의 1/10 까지) - 깊은 폴더에서 이름이 한 글자도 안 보이지 않게 + final indent = list + ? 0.0 + : slim + ? (row.depth * look.indent / 2).clamp(0.0, box.maxWidth * 0.1).toDouble() + : (row.depth * look.indent).clamp(0.0, (box.maxWidth - fixed).clamp(0.0, double.infinity)).toDouble(); // 아주 좁은 창 (412 폭 "폴더 + 파일 목록" 의 트리, 77px 쯤): 아이콘 칸 · 틈을 남은 폭만큼만 (아이콘은 줄여 그림) - 넘치지 않게 final room = (box.maxWidth - indent - (list ? 0 : chevronWidth)).clamp(0.0, double.infinity); - final iconW = math.min(look.rich ? 56.0 : look.iconSize + 6, room); - final gapW = math.min(look.rich ? 8.0 : 6.0, room - iconW); + // 좁은 트리 (slim) 는 큰 썸네일 칸 (56) 대신 작은 아이콘 칸 (28 까지, 아이콘은 줄여 그림) + final iconW = math.min(slim ? math.min(look.iconSize, 24.0) + 4 : look.rich ? 56.0 : look.iconSize + 6, room); + final gapW = math.min(slim ? 4.0 : look.rich ? 8.0 : 6.0, room - iconW); return Row(children: [ SizedBox(width: indent), if (!list) chevron(), @@ -5557,8 +5579,10 @@ class _ExplorerPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(name, maxLines: 1, overflow: TextOverflow.ellipsis, style: nameStyle), - if (look.rich && e.isDir && pane.errors[e.path] != null) + Text(name, maxLines: nameLines, overflow: TextOverflow.ellipsis, style: nameStyle), + if (slim) + const SizedBox.shrink() + else if (look.rich && e.isDir && pane.errors[e.path] != null) Text(tr('읽을 수 없음'), style: dim.copyWith(color: Colors.redAccent)) // 80: 열 없는 모양 (X-plore · 좁은 화면) 은 폴더 이름 아래에 크기 else if (e.isDir && !row.isRoot && !row.isUp && (!look.columns || compact) && _dirSize(pane, e) != null) @@ -5630,7 +5654,9 @@ class _ExplorerPageState extends State { ), ), // 표시 동그라미는 두 번 누르기 영역 밖 (기다리지 않고 바로) - if ((real || (widget.rsync && row.isRoot)) && (_selecting || widget.rsync || _alwaysMarks)) + if (slim) + const SizedBox.shrink() + else if ((real || (widget.rsync && row.isRoot)) && (_selecting || widget.rsync || _alwaysMarks)) SizedBox( width: 36, child: IconButton( diff --git a/test/explorer_text_scale_test.dart b/test/explorer_text_scale_test.dart index 2c2a70f..6fa2cf3 100644 --- a/test/explorer_text_scale_test.dart +++ b/test/explorer_text_scale_test.dart @@ -75,6 +75,46 @@ void main() { await tester.pump(const Duration(seconds: 1)); } + // 관리자 지시 10/20: 좁은 화면의 "폴더 + 파일 목록" - 트리 창 최소 폭 (120px 또는 38%), 트리에는 ✔ 칸 · 크기 줄 없이 이름이 보임 + for (final width in [412.0, 360.0]) { + for (final style in ['xplore', 'windows', 'totalcmd']) { + testWidgets('좁은 폴더 + 파일 목록 ($width 폭, $style): 트리 창이 넓어 폴더 이름이 보이고 ✔ 칸은 오른쪽 목록에만', (tester) async { + tester.view.physicalSize = Size(width, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await open(tester, style, 'sub'); + expect(tester.takeException(), isNull); + // 트리 (먼저 그려짐) 의 'sub' 이름이 다 보인다 + final tree = find.text(style == 'totalcmd' ? '[sub]' : 'sub').first; + final para = tester.renderObject(tree); + // 시험 글꼴 (Ahem) 은 글자마다 글자 크기만큼 넓어 실제보다 훨씬 길다 - 이름 칸 폭으로 본다 (실제 글꼴은 캡처 시험) + expect(para.constraints.maxWidth, greaterThanOrEqualTo(20), reason: '트리의 폴더 이름 칸이 남는다 (예전 412 폭은 0, 실제 글꼴로 360 폭에서 한 줄 5글자쯤)'); + final treeRight = tester.getTopRight(tree).dx; + final listLeft = tester.getTopLeft(find.text(style == 'totalcmd' ? 'a' : 'a.txt')).dx; + expect(listLeft, greaterThan(treeRight), reason: '트리는 왼쪽, 목록은 오른쪽'); + // 트리 줄에는 ✔ 단추가 없다 (오른쪽 목록 줄에만) - X-plore 는 늘 ✔ + if (style == 'xplore') { + final marks = find.byTooltip('표시 (여러 개 고르기)'); + expect(marks, findsWidgets); + for (final m in marks.evaluate()) { + expect(tester.getTopLeft(find.byWidget(m.widget)).dx, greaterThan(treeRight), reason: '✔ 는 오른쪽 목록에만'); + } + } + await close(tester); + }); + } + } + + testWidgets('넓은 화면 (1280) 의 폴더 + 파일 목록은 그대로 (2 : 3, 트리에도 ✔)', (tester) async { + tester.view.physicalSize = const Size(1280, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + await open(tester, 'xplore', 'sub'); + final treeMarks = find.byTooltip('표시 (여러 개 고르기)').evaluate().where((m) => tester.getTopLeft(find.byWidget(m.widget)).dx < 500); + expect(treeMarks, isNotEmpty, reason: '넓은 화면은 지금 그대로'); + await close(tester); + }); + for (final style in ['xplore', 'windows', 'totalcmd']) { final a = style == 'totalcmd' ? 'a' : 'a.txt'; testWidgets('40: $style - 처음 값 (100% · 보통) 은 지금 그대로 · 150% · 넓게는 글자 1.5배 · 줄 높이 늘어남 · 412 폭에서 넘치지 않음', (tester) async { From 4ec505db13ca3fdd4b39362d52a54a9c13356d49 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:51:38 +0900 Subject: [PATCH 19/24] =?UTF-8?q?WIP=20=EA=B3=84=EC=86=8D=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=8B=9C=EA=B3=84=EB=8A=94=20=EA=B7=B8=20=EC=9E=A5?= =?UTF-8?q?=EC=9D=B4=20=EB=8B=A4=20=EB=B3=B4=EC=9D=B8=20=EB=92=A4=EB=B6=80?= =?UTF-8?q?=ED=84=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20,=2028=20=EA=B2=80=ED=86=A0):=20WebDAV=20=EC=B2=98?= =?UTF-8?q?=EB=9F=BC=20=EB=8A=90=EB=A6=B0=20=EC=9B=90=EB=B3=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=9B=EB=8A=94=20=EB=8F=99=EC=95=88=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=9D=B4=20=ED=9D=98=EB=9F=AC=20=EB=B3=B4=EC=A7=80?= =?UTF-8?q?=EB=8F=84=20=EB=AA=BB=ED=95=9C=20=EC=9E=A5=EC=9D=84=20=EB=84=98?= =?UTF-8?q?=EA=B2=BC=EB=8B=A4=20-=20=EC=9E=A5=EB=A7=88=EB=8B=A4=20?= =?UTF-8?q?=ED=95=9C=20=EB=B2=88=20=ED=83=80=EC=9D=B4=EB=A8=B8=20(?= =?UTF-8?q?=EA=B7=B8=EB=A6=BC=20=EC=B2=AB=20=EC=9E=A5=EB=A9=B4=20=C2=B7=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EA=B8=80=EC=9D=B4=20=EA=B7=B8=EB=A0=A4?= =?UTF-8?q?=EC=A7=84=20=EB=92=A4=20=EC=8B=9C=EC=9E=91,=20=EB=B0=9B?= =?UTF-8?q?=EB=8A=94=20=EC=A4=91=EC=97=90=EB=8A=94=20=EC=9E=AC=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=8C)=20=C2=B7=20=EC=8B=9C=ED=97=98:=20=EB=8A=90?= =?UTF-8?q?=EB=A6=B0=20=EC=9B=90=EB=B3=B8=EC=97=90=EC=84=9C=2010=EC=B4=88?= =?UTF-8?q?=20=EB=8F=99=EC=95=88=20=EB=84=98=EA=B8=B0=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0=20=EB=8B=A4=20=EB=B0=9B=EC=9D=80=20=EB=92=A4=203?= =?UTF-8?q?=EC=B4=88=EC=97=90=20=EB=84=98=EA=B9=80,=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=20=EC=8B=9C=ED=97=98=EC=9D=80=20=EB=84=98=EA=B8=B4=20=EB=92=A4?= =?UTF-8?q?=20=ED=95=9C=20=EC=9E=A5=EB=A9=B4=EC=9D=84=20=EB=8D=94=20?= =?UTF-8?q?=EC=A7=80=EB=82=98=EA=B2=8C=20(FutureBuilder=20=EA=B0=80=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=BC=EC=9D=84=20=EB=B0=9B=EC=95=84=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EB=8A=94=20=EC=9E=A5=EB=A9=B4)=20(=EA=B2=80=ED=86=A0?= =?UTF-8?q?=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 --- lib/ui/reader_page.dart | 52 +++++++++++++++++++++++++++++++++----- test/reader_more_test.dart | 49 ++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 7d0d24e..05d158e 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -269,32 +269,57 @@ class _ReaderPageState extends State { /// 계속 보기를 멈추게 한 누름은 장 넘기기 · 막대 숨기기로 쓰지 않는다 bool _swallowTap = false; + /// 관리자 지시 10/20 (28 검토): 다 보인 장 ('$_version:$_index'). 계속 보기 시계는 그 장이 보인 뒤부터 잰다 - + /// WebDAV 처럼 느린 원본에서 받는 동안 시간이 흘러 보지도 못한 장을 넘겼다 + String? _shownKey; + + /// [i] 장이 화면에 다 그려졌다 (그림 첫 장면 · 실패 글). 지금 장이면 계속 보기 시계를 시작 + void _markShown(int i) { + if (!mounted || i != _index) return; + final key = '$_version:$i'; + if (_shownKey == key) return; + _shownKey = key; + if (_playing && _timer == null) _startTimer(); + } + + void _startTimer() { + _timer?.cancel(); + _timer = Timer(Duration(seconds: c.settings.readerAutoSeconds), _tick); + } + void _play(bool on) { _timer?.cancel(); _timer = null; - if (on && _v.isNotEmpty) { - _timer = Timer.periodic(Duration(seconds: c.settings.readerAutoSeconds), (_) => _tick()); - } + // 지금 장이 이미 보이면 바로 재기 시작, 아직 받는 중이면 보인 뒤에 ([_markShown]) + if (on && _v.isNotEmpty && _shownKey == '$_version:$_index') _startTimer(); if (on == _playing) return; setState(() => _playing = on); unawaited(ReaderPage.keepScreenOn(on).catchError((_) {})); } void _tick() { - if (!mounted) return; + _timer = null; + if (!mounted || !_playing) return; if (_index >= _v.length - 1) { _play(false); _snack(tr('마지막 장이라 계속 보기를 멈췄습니다.')); return; } _pages.jumpToPage(_index + 1); + // 다음 장이 이미 받아 둔 것이라 다시 그리지 않으면 그 자리에서 알린다 + WidgetsBinding.instance.addPostFrameCallback((_) => _rearmIfShown()); + } + + /// 넘긴 뒤 그 장이 곧바로 보였는데 (캐시) 알림을 놓쳤으면 시계를 다시 건다 + void _rearmIfShown() { + if (mounted && _playing && _timer == null && _shownKey == '$_version:$_index') _startTimer(); } Future _setSeconds(int d) async { await c.updateSettings((x) => x.readerAutoSeconds = (x.readerAutoSeconds + d).clamp(1, 60)); if (!mounted) return; setState(() {}); - if (_playing) _play(true); // 새 간격으로 + if (_playing) _play(true); // 새 간격으로 (지금 장이 보이면 처음부터 다시) } // ───────── 확대 (30) ───────── @@ -521,13 +546,26 @@ class _ReaderPageState extends State { key: ValueKey('$_version:${slot.page}:${slot.part}:${_retry[slot.page] ?? 0}'), future: _sharp[i] ?? (half ? _loadHalf(slot.page) : _load(slot.page)), builder: (context, snap) { - if (snap.hasError) return Center(child: failed(snap.error)); + // 계속 보기: 받는 동안은 시계를 재지 않고, 보인 (또는 실패 글이 보인) 뒤부터 + void shown() => WidgetsBinding.instance.addPostFrameCallback((_) => _markShown(i)); + if (snap.hasError) { + shown(); + return Center(child: failed(snap.error)); + } final r = snap.data; if (r == null) return const Center(child: CircularProgressIndicator()); + if (r.image != null) shown(); Widget picture = r.image != null ? RawImage(image: r.image, filterQuality: FilterQuality.medium) : Image(image: r.provider!, gaplessPlayback: true, filterQuality: FilterQuality.medium, - errorBuilder: (_, e, _) => failed(e, provider: r.provider)); + frameBuilder: (_, child, frame, sync) { + if (frame != null || sync) shown(); + return child; + }, + errorBuilder: (_, e, _) { + shown(); + return failed(e, provider: r.provider); + }); // 120: 회전 · 상하 반전 (보기만, 파일은 그대로) if (_turns != 0) picture = RotatedBox(quarterTurns: _turns, child: picture); if (_flipV) picture = Transform.flip(flipY: true, child: picture); diff --git a/test/reader_more_test.dart b/test/reader_more_test.dart index d7c7c02..1228b58 100644 --- a/test/reader_more_test.dart +++ b/test/reader_more_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; @@ -28,6 +29,18 @@ class _Sized extends ReaderSource { } } +/// 관리자 지시 10/20: WebDAV 처럼 느린 원본 - [hold] 의 장은 [release] 할 때까지 받는 중 +class _Slow extends _Sized { + final int hold; + final release = Completer(); + _Slow(super.images, this.hold); + @override + Future load(int i, {required int maxWidth}) async { + if (i == hold) await release.future; + return super.load(i, maxWidth: maxWidth); + } +} + /// 223: 폴더의 그림들처럼 (자동 나누기는 다른 그림과 견주어 판단) class _FolderSized extends ImageFilesSource { final List images; @@ -201,15 +214,49 @@ void main() { expect(find.text('1 / 4'), findsOneWidget); await t.pump(const Duration(milliseconds: 200)); expect(find.text('2 / 4'), findsOneWidget); - await t.pump(const Duration(seconds: 2)); + // 관리자 지시 10/20: 시계는 그 장이 다 보인 뒤부터 - 넘긴 뒤 한 장면 (FutureBuilder 가 그림을 받아 그림) 이 지나야 잰다 + await t.pump(); + await t.pump(const Duration(milliseconds: 1900)); + expect(find.text('2 / 4'), findsOneWidget); + await t.pump(const Duration(milliseconds: 200)); + expect(find.text('3 / 4'), findsOneWidget); + await t.pump(); await t.pump(const Duration(seconds: 2)); expect(find.text('4 / 4'), findsOneWidget); + await t.pump(); await t.pump(const Duration(seconds: 2)); expect(find.byTooltip('계속 보기 (2초마다 다음 장)'), findsOneWidget, reason: '마지막 장에서 멈춤'); expect(screenOn, [true, false]); await t.pumpAndSettle(const Duration(seconds: 3)); }); + testWidgets('117 (관리자 지시 10/20): 느린 원본 - 받는 동안은 넘기지 않고, 다 보인 뒤부터 정한 초를 잰다', (t) async { + final c = controller(); + final sized = await source(t, [(100, 200), (100, 200), (100, 200)]); + final src = _Slow(sized.images, 1); + await open(t, c, src); + await t.tap(find.byTooltip('계속 보기 (3초마다 다음 장)')); + await t.pump(); + await t.pump(const Duration(seconds: 3)); + expect(find.text('2 / 3'), findsOneWidget); + // 2 장은 받는 중 - 한참 지나도 넘기지 않는다 + for (var k = 0; k < 10; k++) { + await t.pump(const Duration(seconds: 1)); + } + expect(find.text('2 / 3'), findsOneWidget, reason: '받는 동안 시계가 흐르지 않음'); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + // 다 받으면 그때부터 3초 + src.release.complete(); + await t.pump(); + await t.pump(); + await t.pump(const Duration(milliseconds: 2900)); + expect(find.text('2 / 3'), findsOneWidget); + await t.pump(const Duration(milliseconds: 200)); + expect(find.text('3 / 3'), findsOneWidget); + await t.tap(find.byTooltip('계속 보기 멈추기')); + await t.pumpAndSettle(); + }); + testWidgets('117: 화면을 누르면 멈추고 그 누름으로는 넘기지 않음 · 손으로 밀어도 멈춤', (t) async { final c = controller(); final src = await source(t, [(100, 200), (100, 200), (100, 200), (100, 200)]); From fda636ac32ce9933785bd2aec9ed4f60ba89a996 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:51:39 +0900 Subject: [PATCH 20/24] =?UTF-8?q?WIP=20=EC=8B=9C=ED=97=98=20=ED=9D=94?= =?UTF-8?q?=EB=93=A4=EB=A6=BC:=20dns=20TC=20=EC=8B=9C=ED=97=98=EC=9D=98=20?= =?UTF-8?q?=EA=B0=80=EC=A7=9C=20DNS=20-=20=EB=A7=89=ED=9E=8C=20=ED=8F=AC?= =?UTF-8?q?=ED=8A=B8=20(Windows=2010013,=20Hyper-V=20=EB=A7=89=EC=95=84=20?= =?UTF-8?q?=EB=91=94=20=EB=B2=94=EC=9C=84)=20=EC=9D=98=20UDP=20=EC=97=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=98=A4=EB=A5=98=EA=B0=80=20try=20=EB=B0=96?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20(=EC=9E=A1=ED=9E=88=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EB=B9=84=EB=8F=99=EA=B8=B0=20=EC=98=A4=EB=A5=98)?= =?UTF-8?q?=20=EB=82=98=EC=99=80=20=EC=8B=A4=ED=8C=A8=ED=95=98=EB=8D=98=20?= =?UTF-8?q?=EA=B2=83=20(wt=5Fb3=20=EB=B0=98=EB=B3=B5=203=EB=B2=88=20?= =?UTF-8?q?=EC=A4=91=201=EB=B2=88=20=C2=B7=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EC=A0=84=EC=B2=B4=20=EC=8B=A4=ED=96=89)=20-=20=EC=97=B4?= =?UTF-8?q?=EA=B8=B0=EB=A5=BC=20=EB=94=B0=EB=A1=9C=20zone=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=8F=8C=EB=A0=A4=20=EC=96=B4=EB=8A=90=20=EC=AA=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=98=A4=EB=93=A0=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=EB=A1=9C=20=EB=8B=A4=EC=8B=9C=20(=EA=B2=80?= =?UTF-8?q?=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 --- test/support/fake_dns.dart | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/test/support/fake_dns.dart b/test/support/fake_dns.dart index 83ef0ee..adfc983 100644 --- a/test/support/fake_dns.dart +++ b/test/support/fake_dns.dart @@ -20,6 +20,24 @@ class FakeDns { int get port => _udp.port; + /// 그 번호로 UDP 를 연다 (못 열면 null). 전체 시험 (10/20) 에서 막힌 포트 (10013) 의 오류가 try 밖으로 (잡히지 않은 비동기 오류로) + /// 나와 시험이 실패한 적이 있어, 열기 하나를 따로 영역 (zone) 에서 돌려 어느 쪽으로 오든 "못 열었음" 으로 받는다 + Future _tryUdp(int port) { + final done = Completer(); + runZonedGuarded(() async { + final s = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, port); + if (done.isCompleted) { + s.close(); + } else { + done.complete(s); + } + }, (e, _) { + events.add('udp bind $port failed: ${e is SocketException ? '${e.osError?.errorCode} ${e.message}' : e}'); + if (!done.isCompleted) done.complete(null); + }); + return done.future; + } + Future start() async { if (!truncateUdp) { _udp = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, 0); @@ -29,16 +47,15 @@ class FakeDns { // 그 번호의 UDP 가 막혀 있거나 (10013) 쓰는 중이면 다른 번호로 다시 for (var i = 0;; i++) { final tcp = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); - try { - _udp = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, tcp.port); + final udp = await _tryUdp(tcp.port); + if (udp != null) { + _udp = udp; _tcp = tcp; events.add('bound tcp+udp ${tcp.port} (try $i)'); break; - } on SocketException catch (e) { - events.add('udp bind ${tcp.port} failed: ${e.osError?.errorCode} ${e.message}'); - await tcp.close(); - if (i > 50) rethrow; } + await tcp.close(); + if (i > 50) throw StateError('UDP 포트를 열지 못함: $events'); } } _udp.listen((e) { From f3f1cebbc8625e948ca967f278e9500584463808 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:26:40 +0900 Subject: [PATCH 21/24] =?UTF-8?q?WIP=20=EB=B3=B4=EA=B8=B0=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=82=AC=EC=A7=84=20=EC=A0=95=EB=B3=B4=20=EC=B0=BD?= =?UTF-8?q?:=20=EB=94=94=EB=B2=84=EA=B7=B8=20=EC=9D=98=EB=AF=B8=20?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=20assertion=20('node.built',=2028=20?= =?UTF-8?q?=EC=A7=80=EC=A0=81=2010/20)=20-=20=EC=9E=A5=EC=9D=84=20?= =?UTF-8?q?=EC=86=90=EC=9C=BC=EB=A1=9C=20=EB=84=98=EA=B8=B4=20=EB=92=A4=20?= =?UTF-8?q?=EC=9C=84=20=EB=A7=89=EB=8C=80=20=EB=8B=A8=EC=B6=94=EB=A5=BC=20?= =?UTF-8?q?=EB=88=84=EB=A5=B8=20=EA=B7=B8=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=95=88=EC=97=90=EC=84=9C=20=EC=B0=BD=EC=9D=84=20=EB=9D=84?= =?UTF-8?q?=EC=9A=B0=EB=A9=B4=20=EC=B0=BD=EC=9D=84=20=EB=8B=AB=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=EB=82=AC=EB=8B=A4=20(=EA=B0=A4=EB=9F=AC=EB=A6=AC?= =?UTF-8?q?=20=EC=A4=84=EA=B3=BC=20=EC=83=81=EA=B4=80=EC=97=86=EC=9D=8C=20?= =?UTF-8?q?-=20=EB=81=84=EA=B3=A0=EB=8F=84=20=EB=82=A8,=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=EC=9D=B4=20=EB=9D=84=EC=9A=B4=20=EC=B0=BD=20=C2=B7=20?= =?UTF-8?q?=EB=84=98=EA=B8=B0=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=95=8C?= =?UTF-8?q?=EB=8A=94=20=EC=95=88=20=EB=82=A8).=20=EC=A7=80=EA=B8=88=20?= =?UTF-8?q?=EC=9E=A5=EB=A9=B4=EC=9D=B4=20=EB=81=9D=EB=82=9C=20=EB=92=A4=20?= =?UTF-8?q?=EB=9D=84=EC=9B=80=20(=EC=9E=AC=ED=98=84=204=EB=B2=88=20?= =?UTF-8?q?=E2=86=92=200=EB=B2=88)=20=C2=B7=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=EC=8B=9C=ED=97=98=20integration=5Ftest/reader=5Fdialog=5Fseman?= =?UTF-8?q?tics=5Ftest.dart=20(=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 --- .../reader_dialog_semantics_test.dart | 60 +++++++++++++++++++ lib/ui/reader_page.dart | 5 ++ 2 files changed, 65 insertions(+) create mode 100644 integration_test/reader_dialog_semantics_test.dart diff --git a/integration_test/reader_dialog_semantics_test.dart b/integration_test/reader_dialog_semantics_test.dart new file mode 100644 index 0000000..9470b00 --- /dev/null +++ b/integration_test/reader_dialog_semantics_test.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +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/app/app_controller.dart'; +import 'package:jj_mkvmaker/core/reader_sources.dart'; +import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; +import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; +import 'package:jj_mkvmaker/services/platform_services.dart'; +import 'package:jj_mkvmaker/ui/reader_page.dart'; +import 'package:path/path.dart' as p; + +/// 28 지적 (10/20): 보기 화면에서 장을 손으로 몇 번 넘긴 뒤 위 막대 [사진 정보 (Exif)] 창을 닫으면 디버그 판에서 +/// 의미 트리 assertion ('node.built': is not true, rendering/object.dart) 이 났다. 실제 프레임 · 끌기 애니메이션 (통합 시험) 에서만 나온다. +/// 실행: flutter test integration_test\reader_dialog_semantics_test.dart -d windows +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + for (final gallery in [false, true]) { + testWidgets('장을 넘긴 뒤 사진 정보 창을 열고 닫아도 오류 없음 (갤러리 줄 $gallery)', (t) async { + t.view.physicalSize = const Size(960, 600); + t.view.devicePixelRatio = 1; + addTearDown(t.view.reset); + final tmp = Directory.systemTemp.createTempSync('jj_readersem_'); + addTearDown(() => tmp.deleteSync(recursive: true)); + final files = [ + for (var i = 0; i < 7; i++) + (File(p.join(tmp.path, 'p$i.png'))..writeAsBytesSync(img.encodePng(img.Image(width: 300, height: 200)))).path, + ]; + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.readerGallery = gallery; + Future wait(int ms) async { + for (var i = 0; i < ms ~/ 100; i++) { + await t.runAsync(() => Future.delayed(const Duration(milliseconds: 100))); + await t.pump(); + } + } + + final nav = GlobalKey(); + await t.pumpWidget(MaterialApp(navigatorKey: nav, home: const Scaffold(body: SizedBox.expand()))); + nav.currentState!.push(MaterialPageRoute( + builder: (_) => ReaderPage(c: c, source: ImageFilesSource(files, tempDir: tmp.path, title: 'x'), start: 3))); + await wait(1500); + for (var k = 0; k < 3; k++) { + await t.drag(find.byType(PageView).first, const Offset(-960 * 0.8, 0)); + await wait(500); + } + await t.tap(find.byTooltip('사진 정보 (Exif)').first); + await wait(600); + expect(find.text('이 그림에는 Exif 정보가 없습니다 (JPEG 사진만 읽음)'), findsOneWidget); + await t.tap(find.text('닫기').last); + await wait(500); + expect(t.takeException(), isNull); + await t.pumpWidget(const SizedBox()); + await wait(300); + }); + } +} diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 05d158e..55c9b1f 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -882,6 +882,11 @@ class _ReaderPageState extends State { /// X-plore 맞추기 (관리자 지시 10/20): 지금 그림의 사진 정보 (찍은 날 · 기기 · 크기 · 위치). 위치는 [위치 보기] 를 누를 때만 /// 보이고, 지도 · 인터넷으로 보내지 않는다 (링크 없음) Future _showPhotoInfo() async { + // 28 지적 (10/20): 장을 손으로 몇 번 넘긴 뒤 위 막대 단추를 누른 그 이벤트 안에서 창을 띄우면, 창을 닫을 때 디버그 판에서 + // 의미 트리 assertion ('node.built', rendering/object.dart) 이 났다 (Windows 통합 시험에서 재현 - 넘기지 않거나, 시험에서 따로 띄운 + // 창은 괜찮음). 지금 장면이 끝난 뒤에 띄우면 나지 않는다 (재현 시험 4번 → 0번) + await WidgetsBinding.instance.endOfFrame; + if (!mounted) return; final s = src; if (s is! ImageFilesSource || _v.isEmpty) return; final path = s.paths[_cur.page]; From cb50d284867c5cea6a205929d1741639e01936a4 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:54:47 +0900 Subject: [PATCH 22/24] =?UTF-8?q?WIP=20=ED=81=B0=20=EA=B8=80=EC=9E=90=20?= =?UTF-8?q?=C2=B7=20=EC=A4=84=20=EA=B0=84=EA=B2=A9=20(=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20=EC=A7=80=EC=8B=9C=2010/20,=2028=20=EA=B2=80?= =?UTF-8?q?=ED=86=A0):=20=E2=91=A0=20=EA=B8=80=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EC=BB=A4=EC=84=9C=20(=EA=B8=B0=EA=B8=B0=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=20=C3=97=20=EB=AA=A9=EB=A1=9D=20=EA=B8=80=EC=9E=90=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0)=20=EC=A2=81=EC=9D=80=20=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=20"=ED=8F=B4=EB=8D=94=20+=20=ED=8C=8C=EC=9D=BC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D"=20=EB=91=90=20=EC=B0=BD=EC=9D=B4=20=EB=8B=A4=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=80=EC=A7=80=20=EC=95=8A=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EA=B7=B8=EB=8F=99=EC=95=88=EB=A7=8C=20=ED=95=9C=20?= =?UTF-8?q?=EC=B0=BD=20(=ED=8A=B8=EB=A6=AC=20+=20=EC=9C=84=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=89=EB=8C=80)=20-=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=80=20split=20=EA=B7=B8=EB=8C=80=EB=A1=9C,=20=EB=84=93?= =?UTF-8?q?=EC=96=B4=EC=A7=80=EA=B1=B0=EB=82=98=20=EA=B8=80=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=9E=91=EC=95=84=EC=A7=80=EB=A9=B4=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EB=91=90=20=EC=B0=BD=20(d3=20=ED=8C=90=EB=8B=A8:?= =?UTF-8?q?=20=EB=91=90=20=EC=B0=BD=20=ED=95=84=EC=9A=94=20=ED=8F=AD=20=3D?= =?UTF-8?q?=20182=20+=2090=20=C3=97=20=EA=B8=80=EC=9E=90=20=EB=B0=B0?= =?UTF-8?q?=EC=9C=A8,=201=EB=B0=B0=EB=8A=94=20360=20=ED=8F=AD=EC=97=90?= =?UTF-8?q?=EB=8F=84=20=EB=93=A4=EC=96=B4=EA=B0=80=EA=B3=A0=201.3=20=C3=97?= =?UTF-8?q?=20150%=20=EB=8A=94=20412=20=EC=97=90=EB=8F=84=20=EB=AA=A8?= =?UTF-8?q?=EC=9E=90=EB=9E=8C)=20=E2=91=A1=20=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=9E=90=EB=A6=AC=EB=A5=BC=2048=20?= =?UTF-8?q?=EB=98=90=EB=8A=94=20=EC=A4=84=20=ED=8F=AD=EC=9D=98=2040%=20?= =?UTF-8?q?=EB=A1=9C=20(=EA=B9=8A=EC=9D=80=20=ED=8F=B4=EB=8D=94=20=C2=B7?= =?UTF-8?q?=20=ED=81=B0=20=EA=B8=80=EC=9E=90=EC=97=90=EC=84=9C=20=EB=93=A4?= =?UTF-8?q?=EC=97=AC=EC=93=B0=EA=B8=B0=EC=97=90=20=EB=B0=80=EB=A0=A4=20?= =?UTF-8?q?=EB=AA=87=20=EA=B8=80=EC=9E=90=EB=A7=8C=20=EB=82=A8=EB=8D=98=20?= =?UTF-8?q?=EA=B2=83)=20=E2=91=A2=20=EC=A4=84=20=EA=B0=84=EA=B2=A9=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=EC=97=90=20"=EC=A2=81=EA=B2=8C=EB=8A=94=20?= =?UTF-8?q?=EC=86=90=EA=B0=80=EB=9D=BD=EC=9C=BC=EB=A1=9C=20=EB=88=84?= =?UTF-8?q?=EB=A5=B4=EA=B8=B0=20=EC=96=B4=EB=A0=A4=EC=9A=B8=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EC=8A=B5=EB=8B=88=EB=8B=A4"=20(=ED=95=98=ED=95=9C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=A7=89=EC=A7=80=20=EC=95=8A=EC=9D=8C)?= =?UTF-8?q?=20=C2=B7=20=EC=8B=9C=ED=97=98=20=C2=B7=20=EC=BA=A1=EC=B2=98=20?= =?UTF-8?q?(=EA=B8=B0=EA=B8=B0=201.3)=20(=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=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 --- assets/l10n/en.json | 2 +- assets/l10n/ja.json | 2 +- assets/l10n/zh-Hans.json | 2 +- integration_test/explorer_text_shot_test.dart | 8 +++--- lib/ui/explorer_page.dart | 21 ++++++++++++--- lib/ui/settings_page.dart | 2 +- test/explorer_text_scale_test.dart | 26 +++++++++++++++++++ 7 files changed, 53 insertions(+), 10 deletions(-) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 83d6e0b..346c9e7 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -2698,7 +2698,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer tap: single tap selects → single tap opens. To revert: Settings > File explorer > Tap", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "File explorer two-pane layout: follow screen shape → side by side. To revert: the [Columns ⇆ / Rows ⇅] button in the file explorer, or Pane layout", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "Only the name and size text in the file explorer list. Multiplied by the app screen size and the device text size", - "파일 탐색기 목록의 줄 높이": "Row height in the file explorer list", + "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "Row height in the file explorer list. Compact rows may be hard to tap with a finger", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "You can also change it from the ⋮ menu at the top of the file explorer (side by side · top and bottom · button bar position · buttons)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "File explorer panes: two panes → by screen size (one pane on a portrait phone). To revert: Settings > File explorer > Pane layout", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer: a single tap now opens a file right away (long-press to select or for the menu). To change: Settings > File explorer > Tap", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 876e41a..9b1decb 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -2698,7 +2698,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラーのタップ: 1 回で選択 → 1 回ですぐ開く。元に戻す: 環境設定 > ファイルエクスプローラー > タップ", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "ファイルエクスプローラーの 2 画面配置: 画面の形に合わせる → 左右。元に戻す: ファイルエクスプローラーの [左右 ⇆ / 上下 ⇅] ボタンまたは画面配置", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "ファイルエクスプローラー一覧の名前・サイズの文字のみ。アプリの画面サイズ・端末の文字サイズと掛け合わされます", - "파일 탐색기 목록의 줄 높이": "ファイルエクスプローラー一覧の行の高さ", + "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "ファイルエクスプローラー一覧の行の高さ。狭くすると指でタップしにくい場合があります", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "ファイルエクスプローラー上部の ⋮ メニューからも変えられます (左右 · 上下 · ボタン列の位置 · ボタン構成)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "ファイルエクスプローラーの画面: 2 画面 → 画面サイズに合わせる (縦向きのスマホは 1 画面)。元に戻す: 環境設定 > ファイルエクスプローラー > 画面配置", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラー: ファイルを 1 回タップするとすぐ開きます (選択 · メニューは長押し)。変更: 環境設定 > ファイルエクスプローラー > タップ", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 5157828..45a0ce0 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -2698,7 +2698,7 @@ "파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器点击: 单击选择 → 单击直接打开。恢复: 设置 > 文件浏览器 > 点击", "파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "文件浏览器双窗格布局: 跟随屏幕形状 → 左右。恢复: 文件浏览器中的 [左右 ⇆ / 上下 ⇅] 按钮或窗格布局", "파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "仅文件管理器列表中的名称和大小文字。会与应用界面大小及设备文字大小相乘", - "파일 탐색기 목록의 줄 높이": "文件管理器列表的行高", + "파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "文件管理器列表的行高。紧凑时可能难以用手指点按", "파일 탐색기 위쪽 ⋮ 메뉴에서도 바꿀 수 있습니다 (좌우 · 위아래 · 버튼 줄 위치 · 버튼 구성)": "也可以在文件浏览器顶部的 ⋮ 菜单中更改 (左右 · 上下 · 按钮栏位置 · 按钮配置)", "파일 탐색기 창: 두 창 → 화면 크기 따라 (폰 세로는 한 창). 되돌리기: 환경 설정 > 파일 탐색기 > 창 배치": "文件浏览器窗格: 双窗格 → 按屏幕大小 (竖屏手机为单窗格)。恢复: 设置 > 文件浏览器 > 窗格布局", "파일 탐색기: 파일을 한 번 누르면 바로 엽니다 (고르기 · 메뉴는 길게 누르기). 바꾸기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器:单击文件会立即打开(选择 · 菜单请长按)。更改:设置 > 文件浏览器 > 点按", diff --git a/integration_test/explorer_text_shot_test.dart b/integration_test/explorer_text_shot_test.dart index 20a208c..50bf186 100644 --- a/integration_test/explorer_text_shot_test.dart +++ b/integration_test/explorer_text_shot_test.dart @@ -31,7 +31,7 @@ void main() { final key = GlobalKey(); // 폰처럼 (Android 는 트리 펼치기 화살표가 44 로 넓다 - Windows 로 그리면 폰보다 넉넉해 보인다) debugDefaultTargetPlatformOverride = TargetPlatform.android; - for (final (layout, width) in [('single', 412.0), ('split', 412.0), ('split', 360.0)]) { + for (final (layout, width, device) in [('single', 412.0, 1.0), ('split', 412.0, 1.0), ('split', 360.0, 1.0), ('split', 412.0, 1.3)]) { for (final style in ['xplore', 'windows', 'totalcmd']) { for (final (scale, spacing) in [(100, 'normal'), (150, 'wide'), (80, 'compact')]) { c.settings @@ -43,17 +43,19 @@ void main() { tester.view.devicePixelRatio = 1; await tester.runAsync(() => tester.pumpWidget(MaterialApp( theme: buildTheme(), + // 기기 글자 크기 (관리자 지시 10/20: 1.3 × 150% 에서 두 창이 모자라면 한 창) + builder: (ctx, child) => MediaQuery(data: MediaQuery.of(ctx).copyWith(textScaler: TextScaler.linear(device)), child: child!), home: RepaintBoundary(key: key, child: ExplorerPage(c: c)), ))); for (var i = 0; i < 12; i++) { await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 250))); await tester.pump(); } - expect(tester.takeException(), isNull, reason: '$layout $width $style $scale% $spacing 넘치지 않음'); + expect(tester.takeException(), isNull, reason: '$layout $width ×$device $style $scale% $spacing 넘치지 않음'); final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary; final image = await tester.runAsync(() => boundary.toImage()); final bytes = await tester.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png)); - File('$out/explorer_text_$layout${width.round()}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List()); + File('$out/explorer_text_$layout${width.round()}${device == 1 ? '' : '_dev${(device * 10).round()}'}_${style}_${scale}_$spacing.png').writeAsBytesSync(bytes!.buffer.asUint8List()); await tester.pumpWidget(const SizedBox()); await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 300))); } diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index ecac2a9..c6a39c4 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -1151,7 +1151,11 @@ class _ExplorerPageState extends State { double _rowH() => _look.rowHeightOf(context, textScale: _listTextScale, spacing: rowSpacingFactor(c.settings.explorerRowSpacing)); /// 왼쪽 폴더 트리 + 오른쪽 파일 목록 배치 - bool get _split => !widget.rsync && c.settings.explorerLayout == 'split'; + bool get _split => !widget.rsync && c.settings.explorerLayout == 'split' && !_splitTight; + + /// 관리자 지시 10/20 (28 검토): 글자가 커서 (기기 글자 × 목록 글자 크기) 좁은 화면에 트리 · 목록 두 창이 다 들어가지 않으면 + /// 그동안만 한 창 (트리 + 위 경로 막대) 으로 보인다 - 고른 배치 (설정) 는 그대로, 화면이 넓어지거나 글자가 작아지면 다시 두 창. [_body] 가 정한다 + bool _splitTight = false; /// 관리자 지시 10/20: 좁은 화면 (폭 < [narrowSplitWidth]) 의 "폴더 + 파일 목록" 좌우 배치 - 트리 창에 최소 폭을 두고 /// ✔ 칸 · 크기 줄을 숨긴다 (412 폭에서 트리가 77px 이라 폴더 이름이 한 글자도 안 보였다). [_body] 가 정한다 @@ -4581,8 +4585,18 @@ class _ExplorerPageState extends State { _shape = shape; } final dual = _dual; - final split = _split; return LayoutBuilder(builder: (context, box) { + // 두 창이 다 들어가는지: 트리 (화살표 · 아이콘 · 여백 78 + 이름 40) + 목록 (아이콘 · ✔ · 여백 104 + 이름 50), 이름은 글자 배율만큼. + // 1배면 360 폭에도 들어가고, 기기 글자 1.3 × 150% (약 2배) 면 412 폭에도 모자란다 + final wantSplit = !widget.rsync && s.explorerLayout == 'split'; + final textScale = MediaQuery.textScalerOf(context).scale(14) / 14 * _listTextScale; + final barW = s.explorerToolbar == 'hidden' ? 0.0 : 72.0; + final sideNow = switch (s.explorerOrientation) { 'side' => true, 'stacked' => false, _ => box.maxWidth >= box.maxHeight }; + final tightWas = _splitTight; + _splitTight = wantSplit && sideNow && box.maxWidth - barW < 182 + 90 * textScale; + // 바뀌면 이 builder 밖 (버튼 이름 "두 창" 등) 도 한 번 다시 그린다 + if (tightWas != _splitTight) WidgetsBinding.instance.addPostFrameCallback((_) => mounted ? setState(() {}) : null); + final split = _split; // 폭이 바뀌면 (좁은 한 창 ↔ 두 창) 이 builder 만 다시 불린다 - 그때도 새로 붙은 창을 보이게 if (_ready) _revealNewViews(); final side = !(dual || split) || @@ -5540,7 +5554,8 @@ class _ExplorerPageState extends State { // 좁은 창 (폰 세로의 좌우 두 창) 에서 깊은 폴더: 들여쓰기는 이름 자리를 남기는 만큼만 (넘치지 않게) Widget nameCell() => LayoutBuilder(builder: (context, box) { - final fixed = (list ? 0 : chevronWidth) + (look.rich ? 64 : look.iconSize + 12) + 48.0; + // 이름 자리는 48 또는 줄 폭의 40% (관리자 지시 10/20: 글자가 크고 폴더가 깊으면 들여쓰기에 밀려 이름이 몇 글자만 남았다) + final fixed = (list ? 0 : chevronWidth) + (look.rich ? 64 : look.iconSize + 12) + math.max(48.0, box.maxWidth * 0.4); // 좁은 트리 (slim): 들여쓰기는 반만 (폭의 1/10 까지) - 깊은 폴더에서 이름이 한 글자도 안 보이지 않게 final indent = list ? 0.0 diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 7778aa0..3708e9a 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -913,7 +913,7 @@ class _SettingsPageState extends State { ), SettingTile( title: Text(tr('줄 간격')), - subtitle: Text(tr('파일 탐색기 목록의 줄 높이')), + subtitle: Text(tr('파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다')), trailing: DropdownButton( value: s.explorerRowSpacing, items: [ diff --git a/test/explorer_text_scale_test.dart b/test/explorer_text_scale_test.dart index 6fa2cf3..c9cceac 100644 --- a/test/explorer_text_scale_test.dart +++ b/test/explorer_text_scale_test.dart @@ -105,6 +105,32 @@ void main() { } } + // 관리자 지시 10/20 (28 검토): 글자가 아주 크면 (기기 1.3 × 150%) 좁은 화면에 두 창이 다 들어가지 않는다 - 그동안만 한 창 (설정은 그대로) + for (final style in ['xplore', 'windows', 'totalcmd']) { + testWidgets('글자가 아주 크면 (기기 1.3 × 150% · 넓게, 412 폭, $style) 폴더 + 파일 목록 대신 한 창 - 설정은 split 그대로 · 넘침 없음', (tester) async { + tester.view.physicalSize = const Size(412, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final sub = style == 'totalcmd' ? '[sub]' : 'sub'; + final a = style == 'totalcmd' ? 'a' : 'a.txt'; + final c = await open(tester, style, sub, device: 1.3, scale: 150, spacing: 'wide'); + await tester.pump(); + expect(tester.takeException(), isNull); + expect(c.settings.explorerLayout, 'split', reason: '고른 배치는 바꾸지 않는다'); + expect(find.text(sub), findsOneWidget, reason: '트리 · 목록 두 창이 아니라 한 창'); + expect(find.text(a), findsOneWidget, reason: '한 창 트리에 파일도 보인다'); + final para = tester.renderObject(find.text(sub)); + // X-plore 는 이름이 줄 폭을 다 쓰고, Windows · TC 는 열 (날짜 · 유형 · 크기) 이 있어 이름 열이 6/13 쯤 + expect(para.constraints.maxWidth, greaterThan(style == 'xplore' ? 100 : 55), reason: '이름이 보인다'); + await close(tester); + // 글자가 보통이면 같은 폭에서 두 창 + await open(tester, style, sub); + await tester.pump(); + expect(find.text(sub), findsNWidgets(2), reason: '보통 글자면 폴더 + 파일 목록'); + await close(tester); + }); + } + testWidgets('넓은 화면 (1280) 의 폴더 + 파일 목록은 그대로 (2 : 3, 트리에도 ✔)', (tester) async { tester.view.physicalSize = const Size(1280, 900); tester.view.devicePixelRatio = 1; From 77710c2a5ffe324c1d2f28e0e815c96fe0ac7854 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 01:45:26 +0900 Subject: [PATCH 23/24] =?UTF-8?q?WIP=20=EC=A2=81=EC=9D=80=20=EC=B0=BD=20?= =?UTF-8?q?=EC=97=B4=20=EC=88=A8=EA=B8=B0=EA=B8=B0=20=C2=B7=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=ED=99=94=EB=A9=B4=20=EC=B0=BD=20=EB=9D=84=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=2010/20):=20=E2=91=A0=20Windows=20=C2=B7=20TC=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=EC=97=90=EC=84=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=97=B4=EC=9D=B4=20120=20(=C3=97=20=EA=B8=80=EC=9E=90=20?= =?UTF-8?q?=EB=B0=B0=EC=9C=A8)=20=EB=B3=B4=EB=8B=A4=20=EC=A2=81=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EC=9C=A0=ED=98=95=20(TC=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?=EC=9E=90)=20=E2=86=92=20=EB=82=A0=EC=A7=9C=20=EC=88=9C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=A8=EA=B9=80,=20=ED=81=AC=EA=B8=B0?= =?UTF-8?q?=EB=8A=94=20=EB=82=A8=EA=B9=80=20-=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=B4=20=EB=AA=A8=EC=9E=90=EB=9E=84=20=EB=95=8C=EB=A7=8C,?= =?UTF-8?q?=20=EB=84=93=EC=96=B4=EC=A7=80=EB=A9=B4=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EB=B3=B4=EC=9E=84,=20=EC=97=B4=20=EC=84=A4=EC=A0=95=EC=9D=80?= =?UTF-8?q?=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20(=EC=97=B4=20=EB=A8=B8?= =?UTF-8?q?=EB=A6=AC=20=C2=B7=20=EC=A4=84=EC=9D=B4=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EC=B0=BD=20=ED=8F=AD=EC=9C=BC=EB=A1=9C=20=EC=A0=95=ED=95=A8)?= =?UTF-8?q?=20=C2=B7=20=EA=B8=80=EC=9E=90=20=EB=B0=B0=EC=9C=A8=EC=9D=84=20?= =?UTF-8?q?=EA=B3=B1=ED=95=9C=20=EA=B2=83=EC=9D=80=20d3=20=ED=8C=90?= =?UTF-8?q?=EB=8B=A8=20(1.95=20=EB=B0=B0=20=EA=B8=80=EC=9E=90=EC=9D=98=201?= =?UTF-8?q?20px=20=EC=9D=80=20=EB=84=A4=20=EA=B8=80=EC=9E=90=EC=AF=A4)=20?= =?UTF-8?q?=E2=91=A1=20=EB=B3=B4=EA=B8=B0=20=ED=99=94=EB=A9=B4=20PDF=20?= =?UTF-8?q?=EC=B0=BD=20(=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A7=80=EC=9A=B0?= =?UTF-8?q?=EA=B8=B0=20=C2=B7=20=EA=B7=B8=EB=A6=BC=20=EB=84=A3=EA=B8=B0)?= =?UTF-8?q?=20=EA=B3=BC=20=EC=A0=80=EC=9E=A5=20=EC=95=88=20=ED=95=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=B0=BD=EB=8F=84=20=EC=A7=80=EA=B8=88=20?= =?UTF-8?q?=EC=9E=A5=EB=A9=B4=EC=9D=B4=20=EB=81=9D=EB=82=9C=20=EB=92=A4=20?= =?UTF-8?q?=EB=9D=84=EC=9B=80=20('node.built'=20=EC=99=80=20=EA=B0=99?= =?UTF-8?q?=EC=9D=80=20=EB=AA=A8=EC=96=91=20-=20=EB=AF=B8=EB=A6=AC=20?= =?UTF-8?q?=EB=A7=89=EC=9D=8C,=20=EC=A0=80=EC=9E=A5=20=EC=95=88=20?= =?UTF-8?q?=ED=95=9C=20=EB=B3=80=EA=B2=BD=20=EC=B0=BD=EC=9D=80=20d3=20?= =?UTF-8?q?=ED=8C=90=EB=8B=A8)=20=C2=B7=20=EC=8B=9C=ED=97=98=20(=EA=B2=80?= =?UTF-8?q?=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 --- lib/ui/explorer_look.dart | 31 +++++++++++++++++++----- lib/ui/explorer_page.dart | 37 ++++++++++++++++++++++------- lib/ui/reader_page.dart | 16 +++++++++---- test/explorer_text_scale_test.dart | 38 ++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 19 deletions(-) diff --git a/lib/ui/explorer_look.dart b/lib/ui/explorer_look.dart index 525c0dc..581d620 100644 --- a/lib/ui/explorer_look.dart +++ b/lib/ui/explorer_look.dart @@ -78,6 +78,23 @@ class _TimesScaler extends TextScaler { int get hashCode => Object.hash(base, factor); } +/// 관리자 지시 10/20: 좁은 창의 열 (Windows 탐색기 · Total Commander) - 이름 열이 120 (× 글자 배율) 보다 좁으면 유형 (TC 는 확장자) → +/// 날짜 순으로 숨긴다 (크기는 남김). 0 = 다 보임 · 1 = 유형 숨김 · 2 = 유형 · 날짜 숨김. 화면이 모자랄 때만 - 열 설정은 그대로. +/// [width] 창 폭, [marks] 줄 끝 ✔ 칸 (36) 이 있는지, [textScale] 목록 글자 배율 (기기 × 목록 글자 크기) +int columnHideLevel(ExplorerStyle style, double width, {bool marks = false, double textScale = 1}) { + if (!style.columns) return 0; + // 이름 열 120 (글자가 크면 그만큼 - 1.95 배 글자의 120 은 네 글자쯤이었다) + final minName = 120 * (textScale < 1 ? 1 : textScale); + final inner = width - 16 - (marks ? 36 : 0); + // 열 비율: Windows 이름 6 · 날짜 3 · 유형 2 · 크기 2, TC 이름 6 · 확장자 1 · 크기 2 · (8px) · 날짜 3 + final flexes = style == ExplorerStyle.windows ? [13, 11, 8] : [12, 11, 8]; + final extra = style == ExplorerStyle.totalcmd ? 8 : 0; + for (var level = 0; level < 2; level++) { + if ((inner - (level < 2 ? extra : 0)) * 6 / flexes[level] >= minName) return level; + } + return 2; +} + /// 파일 종류별 아이콘 · 색 (스타일마다 다르게) (IconData, Color) fileIcon(ExplorerStyle style, FileEntry e, {bool open = false}) { final x = e.ext; @@ -146,6 +163,9 @@ class ExplorerColumnsHeader extends StatelessWidget { /// 선택 모드: 줄 끝의 선택 동그라미 칸만큼 비운다 final bool markColumn; + /// 관리자 지시 10/20: 좁아서 숨긴 열 ([columnHideLevel]: 1 = 유형, 2 = 유형 · 날짜) + final int hide; + /// 84: 지금 정렬 ('name' · 'date' · 'size' · 'type') · 거꾸로 - 그 열에 ▲ ▼ final String? sortBy; final bool sortDesc; @@ -153,7 +173,7 @@ class ExplorerColumnsHeader extends StatelessWidget { /// 84: 열 머리를 누르면 (같은 열을 다시 누르면 거꾸로 - 부르는 쪽이 정한다) final void Function(String by)? onSort; const ExplorerColumnsHeader( - {super.key, required this.style, this.markColumn = false, this.sortBy, this.sortDesc = false, this.onSort}); + {super.key, required this.style, this.markColumn = false, this.hide = 0, this.sortBy, this.sortDesc = false, this.onSort}); @override Widget build(BuildContext context) { @@ -193,16 +213,15 @@ class ExplorerColumnsHeader extends StatelessWidget { children: style == ExplorerStyle.windows ? [ cell(tr('이름'), 6, by: 'name'), - cell(tr('수정한 날짜'), 3, by: 'date'), - cell(tr('유형'), 2, by: 'type'), + if (hide < 2) cell(tr('수정한 날짜'), 3, by: 'date'), + if (hide < 1) cell(tr('유형'), 2, by: 'type'), cell(tr('크기'), 2, align: TextAlign.end, by: 'size'), ] : [ cell(tr('이름'), 6, by: 'name'), - cell(tr('확장자'), 1, by: 'type'), + if (hide < 1) cell(tr('확장자'), 1, by: 'type'), cell(tr('크기'), 2, align: TextAlign.end, by: 'size'), - const SizedBox(width: 8), - cell(tr('날짜'), 3, by: 'date'), + if (hide < 2) ...[const SizedBox(width: 8), cell(tr('날짜'), 3, by: 'date')], ], ), ); diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index c6a39c4..95ddffc 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -234,6 +234,9 @@ enum ExplorerButton { class _Pane extends ChangeNotifier { String root; + /// 관리자 지시 10/20: 좁아서 숨긴 열 ([columnHideLevel]) - 열을 그리는 창 (목록) 의 폭으로 정하고 열 머리 · 줄이 같이 쓴다 + int colHide = 0; + /// 지금 폴더. 75: 바뀔 때마다 뒤로 가기 내역에 (뒤로 · 앞으로로 움직일 때는 빼고) String get current => _current; set current(String v) { @@ -4929,7 +4932,13 @@ class _ExplorerPageState extends State { final rows = _rows(pane, foldersOnly: foldersOnly); final look = _look; // 38: 창 높이를 알아야 안내 상자의 높이를 제한할 수 있다 - return LayoutBuilder(builder: (context, box) => _frame(i, [ + return LayoutBuilder(builder: (context, box) { + // 열을 그리는 창만 숨길 열을 정한다 ("폴더 + 파일 목록" 의 트리는 열이 없고 목록과 같은 창을 쓴다) + if (look.columns && !foldersOnly) { + pane.colHide = columnHideLevel(look, box.maxWidth, + marks: _selecting, textScale: MediaQuery.textScalerOf(context).scale(14) / 14 * _listTextScale); + } + return _frame(i, [ _paneHeader(pane), const Divider(height: 1), // 보이는 수는 맞는 것만 (맞는 것이 든 위 폴더는 빼고) @@ -4941,6 +4950,7 @@ class _ExplorerPageState extends State { ExplorerColumnsHeader( style: look, markColumn: _selecting, + hide: pane.colHide, sortBy: _sortOf(pane.current).$1, sortDesc: _sortOf(pane.current).$2, onSort: (by) => _sortByColumn(pane, by), @@ -4962,7 +4972,8 @@ class _ExplorerPageState extends State { ]), ), if ((_selecting || (_alwaysMarks && pane.marked.isNotEmpty)) && !foldersOnly) _marksBar(pane), - ])); + ]); + }); } /// 76: 목록 줄의 키 = 항목 경로. 키가 없으면 줄 목록이 바뀔 때 (빠른 필터 · 펼치기 · 새로 고침) 같은 자리의 줄 위젯을 @@ -4992,7 +5003,12 @@ class _ExplorerPageState extends State { ]; final look = _look; // 38: 창 높이를 알아야 안내 상자의 높이를 제한할 수 있다 - return LayoutBuilder(builder: (context, box) => _frame(0, [ + return LayoutBuilder(builder: (context, box) { + if (look.columns) { + pane.colHide = columnHideLevel(look, box.maxWidth, + marks: _selecting, textScale: MediaQuery.textScalerOf(context).scale(14) / 14 * _listTextScale); + } + return _frame(0, [ Container( height: 40, alignment: Alignment.centerLeft, @@ -5006,6 +5022,7 @@ class _ExplorerPageState extends State { ExplorerColumnsHeader( style: look, markColumn: _selecting, + hide: pane.colHide, sortBy: _sortOf(dir).$1, sortDesc: _sortOf(dir).$2, onSort: (by) => _sortByColumn(pane, by), @@ -5026,7 +5043,8 @@ class _ExplorerPageState extends State { )), ), if (_selecting) _marksBar(pane, listDir: dir), - ])); + ]); + }); } /// 76: 빠른 필터 열기 ([text] 가 있으면 그 글자로 시작) @@ -5613,21 +5631,22 @@ class _ExplorerPageState extends State { flex: flex, child: Text(t, style: dim, textAlign: align, maxLines: 1, overflow: TextOverflow.ellipsis), ); + // 관리자 지시 10/20: 좁으면 유형 → 날짜 열을 숨김 (크기는 남김, [columnHideLevel]) + final hide = pane.colHide; final cells = !look.columns || compact || !real ? [Expanded(child: nameCell())] : look == ExplorerStyle.windows ? [ Expanded(flex: 6, child: nameCell()), - cell(_date(e.modified), 3), - cell(typeLabel(e), 2), + if (hide < 2) cell(_date(e.modified), 3), + if (hide < 1) cell(typeLabel(e), 2), cell(e.isDir ? (_dirSize(pane, e) ?? '') : formatSize(e.size), 2, align: TextAlign.end), ] : [ Expanded(flex: 6, child: nameCell()), - cell(e.isDir ? '' : e.ext, 1), + if (hide < 1) cell(e.isDir ? '' : e.ext, 1), cell(e.isDir ? (_dirSize(pane, e) ?? '') : formatSize(e.size), 2, align: TextAlign.end), - const SizedBox(width: 8), - cell(_date(e.modified), 3), + if (hide < 2) ...[const SizedBox(width: 8), cell(_date(e.modified), 3)], ]; final paneIndex = _panes.indexOf(pane); diff --git a/lib/ui/reader_page.dart b/lib/ui/reader_page.dart index 55c9b1f..e61e346 100644 --- a/lib/ui/reader_page.dart +++ b/lib/ui/reader_page.dart @@ -609,7 +609,13 @@ class _ReaderPageState extends State { PdfSource? get _pdf => src is PdfSource ? src as PdfSource : null; + /// 28 지적 (10/20): 위 막대 단추를 누른 그 이벤트 안에서 창을 띄우면 (장을 손으로 넘긴 뒤) 창을 닫을 때 디버그 판에서 + /// 의미 트리 assertion ('node.built') 이 났다 - 창은 지금 장면이 끝난 뒤에 띄운다 (부른 쪽은 뒤에 mounted 확인) + Future _afterFrame() => WidgetsBinding.instance.endOfFrame; + Future _deletePage() async { + await _afterFrame(); + if (!mounted) return; final pdf = _pdf!; if (pdf.length <= 1) { _snack(tr('마지막 한 페이지는 지울 수 없습니다.')); @@ -634,6 +640,8 @@ class _ReaderPageState extends State { } Future _insertImages() async { + await _afterFrame(); + if (!mounted) return; final pdf = _pdf!; final page = _cur.page; final files = await c.services.storage.pickFiles(title: tr('PDF 에 넣을 그림 선택'), extensions: c.settings.imageExts); @@ -662,6 +670,8 @@ class _ReaderPageState extends State { Future _confirmLeave() async { final pdf = _pdf; if (pdf == null || !pdf.dirty) return true; + await _afterFrame(); + if (!mounted) return false; final r = await showDialog( context: context, builder: (ctx) => AlertDialog( @@ -882,10 +892,8 @@ class _ReaderPageState extends State { /// X-plore 맞추기 (관리자 지시 10/20): 지금 그림의 사진 정보 (찍은 날 · 기기 · 크기 · 위치). 위치는 [위치 보기] 를 누를 때만 /// 보이고, 지도 · 인터넷으로 보내지 않는다 (링크 없음) Future _showPhotoInfo() async { - // 28 지적 (10/20): 장을 손으로 몇 번 넘긴 뒤 위 막대 단추를 누른 그 이벤트 안에서 창을 띄우면, 창을 닫을 때 디버그 판에서 - // 의미 트리 assertion ('node.built', rendering/object.dart) 이 났다 (Windows 통합 시험에서 재현 - 넘기지 않거나, 시험에서 따로 띄운 - // 창은 괜찮음). 지금 장면이 끝난 뒤에 띄우면 나지 않는다 (재현 시험 4번 → 0번) - await WidgetsBinding.instance.endOfFrame; + // 지금 장면이 끝난 뒤에 띄운다 ([_afterFrame], Windows 통합 시험에서 재현 4번 → 0번) + await _afterFrame(); if (!mounted) return; final s = src; if (s is! ImageFilesSource || _v.isEmpty) return; diff --git a/test/explorer_text_scale_test.dart b/test/explorer_text_scale_test.dart index c9cceac..6e7b36d 100644 --- a/test/explorer_text_scale_test.dart +++ b/test/explorer_text_scale_test.dart @@ -9,6 +9,7 @@ import 'package:jj_mkvmaker/app/settings.dart'; import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; import 'package:jj_mkvmaker/services/platform_services.dart'; +import 'package:jj_mkvmaker/ui/explorer_look.dart'; import 'package:jj_mkvmaker/ui/explorer_page.dart'; import 'package:path/path.dart' as p; @@ -131,6 +132,43 @@ void main() { }); } + // 관리자 지시 10/20: 좁은 창의 열 - 이름 열이 120 보다 좁으면 유형 (TC 확장자) → 날짜 순으로 숨김, 크기는 남김 + test('숨길 열 정하기: 넓으면 0 · 좁아질수록 유형 → 날짜 · 열 없는 X-plore 는 0', () { + expect(columnHideLevel(ExplorerStyle.windows, 1280), 0); + expect(columnHideLevel(ExplorerStyle.windows, 300), 0, reason: '이름 열 (300-16)*6/13 = 131'); + expect(columnHideLevel(ExplorerStyle.windows, 300, marks: true), 1, reason: '✔ 칸 (36) 이 있으면 114'); + expect(columnHideLevel(ExplorerStyle.windows, 260), 1); + expect(columnHideLevel(ExplorerStyle.windows, 180), 2); + expect(columnHideLevel(ExplorerStyle.totalcmd, 1280), 0); + expect(columnHideLevel(ExplorerStyle.totalcmd, 180), 2); + expect(columnHideLevel(ExplorerStyle.xplore, 100), 0); + }); + + for (final style in ['windows', 'totalcmd']) { + testWidgets('좁은 한 창 ($style, 412 · 기기 글자 1.3): 이름 열이 좁으면 유형 → 날짜 열을 숨기고 크기는 남김 · 넓으면 다 보임', (tester) async { + tester.view.physicalSize = const Size(412, 900); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final type = style == 'windows' ? '유형' : '확장자'; + final date = style == 'windows' ? '수정한 날짜' : '날짜'; + final sub = style == 'totalcmd' ? '[sub]' : 'sub'; + final c = await open(tester, style, sub, device: 1.3, scale: 150); + await tester.pump(); + expect(c.settings.explorerLayout, 'split'); + expect(tester.takeException(), isNull); + expect(find.text(type), findsNothing, reason: '좁아서 유형 열 숨김'); + expect(find.text('크기'), findsOneWidget, reason: '크기는 남김'); + await close(tester); + tester.view.physicalSize = const Size(1600, 900); + await open(tester, style, sub); + await tester.pump(); + expect(find.text(type), findsOneWidget); + expect(find.text(date), findsOneWidget); + expect(find.text('크기'), findsOneWidget); + await close(tester); + }); + } + testWidgets('넓은 화면 (1280) 의 폴더 + 파일 목록은 그대로 (2 : 3, 트리에도 ✔)', (tester) async { tester.view.physicalSize = const Size(1280, 900); tester.view.devicePixelRatio = 1; From 189e51b45eb31ea6c94d8db6af798e4965c4b944 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 01:45:27 +0900 Subject: [PATCH 24/24] =?UTF-8?q?WIP=20=EC=8B=9C=ED=97=98=20=ED=9D=94?= =?UTF-8?q?=EB=93=A4=EB=A6=BC=20(10/21=20=EC=A0=84=EC=B2=B4=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89):=20=E2=91=A0=20=EA=B0=80=EC=A7=9C=20DNS=20-=20OS=20?= =?UTF-8?q?=EA=B0=80=20TCP=20=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=B0=A8?= =?UTF-8?q?=EB=A1=80=EB=A1=9C=20=EC=A3=BC=EB=8A=94=EB=8D=B0=20=EA=B7=B8=20?= =?UTF-8?q?=EB=91=98=EB=A0=88=EA=B0=80=20=ED=86=B5=EC=A7=B8=EB=A1=9C=20UDP?= =?UTF-8?q?=20=EB=A7=89=ED=9E=8C=20=EB=B2=94=EC=9C=84=20(10013)=20?= =?UTF-8?q?=EB=9D=BC=2050=EB=B2=88=20=EB=AA=A8=EB=91=90=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=E2=86=92=20=ED=99=80=EC=88=98=20=EB=B2=88=EC=A7=B8?= =?UTF-8?q?=EB=8A=94=20UDP=20=EB=A5=BC=20=EB=A8=BC=EC=A0=80=20OS=20?= =?UTF-8?q?=EA=B0=80=20=EA=B3=A0=EB=A5=B8=20=EB=B2=88=ED=98=B8=EB=A1=9C=20?= =?UTF-8?q?=EC=97=B4=EA=B3=A0=20=EA=B7=B8=20=EB=B2=88=ED=98=B8=EB=A1=9C=20?= =?UTF-8?q?TCP=20=E2=91=A1=20QueuedHwpDoc=20"=EC=A0=9C=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=EB=B6=80=ED=84=B0=20=EC=9E=B0=EB=8B=A4"=20-=20?= =?UTF-8?q?=EB=92=A4=20=EB=AC=B6=EC=9D=8C=20=EC=A0=9C=ED=95=9C=20300ms=20?= =?UTF-8?q?=EA=B0=80=20=EB=AC=B4=EA=B1=B0=EC=9A=B8=20=EB=95=8C=20=EB=86=93?= =?UTF-8?q?=EC=95=84=EC=A3=BC=EA=B8=B0=EA=B9=8C=EC=A7=80=EC=9D=98=20?= =?UTF-8?q?=ED=8B=88=EB=B3=B4=EB=8B=A4=20=EC=A7=A7=EC=95=98=EB=8B=A4=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=9C=ED=95=9C=201.5=EC=B4=88=20=C2=B7=20?= =?UTF-8?q?=EC=A4=84=EC=97=90=EC=84=9C=202=EC=B4=88=20=EA=B8=B0=EB=8B=A4?= =?UTF-8?q?=EB=A6=BC=20(=EB=9C=BB=EC=9D=80=20=EA=B0=99=EC=9D=8C)=20=C2=B7?= =?UTF-8?q?=203=EB=B2=88=20=EB=B0=98=EB=B3=B5=20=ED=86=B5=EA=B3=BC=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 --- test/hwp_source_unit_test.dart | 5 +++-- test/support/fake_dns.dart | 33 ++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/test/hwp_source_unit_test.dart b/test/hwp_source_unit_test.dart index 1dbf0ac..0c7824c 100644 --- a/test/hwp_source_unit_test.dart +++ b/test/hwp_source_unit_test.dart @@ -231,8 +231,9 @@ void main() { final n = _Gated(); final d = QueuedHwpDoc(n); final a = d.exportPdf(0, 5, 'a', timeout: const Duration(seconds: 30)); // 오래 도는 앞 묶음 - final b = d.exportPdf(5, 10, 'b', timeout: const Duration(milliseconds: 300)); - await Future.delayed(const Duration(milliseconds: 700)); // 뒤 묶음의 제한보다 오래 줄에서 기다림 + // 흔들림 (10/21 전체 실행): 제한이 300ms 면 무거울 때 [release] 까지의 틈이 그것을 넘었다 - 제한 1.5초 · 줄에서 2초 기다림 + final b = d.exportPdf(5, 10, 'b', timeout: const Duration(milliseconds: 1500)); + await Future.delayed(const Duration(milliseconds: 2000)); // 뒤 묶음의 제한보다 오래 줄에서 기다림 expect(n.started, [1]); n.release(1); await a; diff --git a/test/support/fake_dns.dart b/test/support/fake_dns.dart index adfc983..404dc28 100644 --- a/test/support/fake_dns.dart +++ b/test/support/fake_dns.dart @@ -46,16 +46,31 @@ class FakeDns { // netsh int ipv4 show excludedportrange) 가 달라, TCP 를 먼저 OS 가 고른 번호로 열고 그 번호로 UDP 를 연다. // 그 번호의 UDP 가 막혀 있거나 (10013) 쓰는 중이면 다른 번호로 다시 for (var i = 0;; i++) { - final tcp = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); - final udp = await _tryUdp(tcp.port); - if (udp != null) { - _udp = udp; - _tcp = tcp; - events.add('bound tcp+udp ${tcp.port} (try $i)'); - break; + // 10/21 전체 실행: OS 가 TCP 번호를 차례로 주는데 (62345, 62346 …) 그 둘레가 통째로 UDP 막힌 범위라 50번 모두 실패했다 - + // 홀수 번째는 거꾸로 UDP 를 OS 가 고른 (열 수 있는) 번호로 먼저 열고 그 번호로 TCP 를 연다 + if (i.isOdd) { + final udp = await RawDatagramSocket.bind(InternetAddress.loopbackIPv4, 0); + try { + _tcp = await ServerSocket.bind(InternetAddress.loopbackIPv4, udp.port); + _udp = udp; + events.add('bound udp+tcp ${udp.port} (try $i)'); + break; + } on SocketException catch (e) { + events.add('tcp bind ${udp.port} failed: ${e.osError?.errorCode} ${e.message}'); + udp.close(); + } + } else { + final tcp = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); + final udp = await _tryUdp(tcp.port); + if (udp != null) { + _udp = udp; + _tcp = tcp; + events.add('bound tcp+udp ${tcp.port} (try $i)'); + break; + } + await tcp.close(); } - await tcp.close(); - if (i > 50) throw StateError('UDP 포트를 열지 못함: $events'); + if (i > 50) throw StateError('UDP · TCP 같은 포트를 열지 못함: $events'); } } _udp.listen((e) {