initial commit

This commit is contained in:
2026-09-21 13:41:40 +09:00
commit 855c7328df
411 changed files with 85352 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
# DocView アプリケーション設計書
設計基準版 1.0 / 2026年9月19日
## 目的と設計の要点
DocView(仮称)は、PDFを中心にHTML・HTML ZIP・EPUBを読むための、Vim風操作を備えたデスクトップ文書リーダーである。本文を主役にした静かな画面と、キーボードだけで完結する移動を提供する。
表示基盤にはQt Quick、PDF描画には独立プロセス内のPDFium、HTML・EPUB表示にはQt WebEngineを採用する設計とする。PDFは紙面を保持し、HTML・EPUBはそれぞれの構造を使って移動する。表示形式の違いを無理に「ページ番号」だけへ統一しない。
本版は実装の入力となる設計基準である。動作・性能・互換性の実測結果は含まない。性能数値は受入目標、技術選定は検証ゲート付きの設計判断として示す。プログラム、ビルド環境、試作品は成果物に含まれない。
## 文書構成
| 文書 | 主な内容 | 主な読者 |
|---|---|---|
| [01 要件・スコープ](01-requirements.md) | 要件ID、優先順位、対応範囲、受入追跡 | 企画・開発・QA |
| [02 全体アーキテクチャ](02-architecture.md) | 構成、責務、処理フロー、拡張点 | 開発 |
| [03 形式別設計](03-formats.md) | PDF、HTML、ZIP、EPUBの取込みと表示 | 開発・QA |
| [04 画面・操作設計](04-ui-navigation.md) | 画面、モード、キー、フォーカス、見出し | 企画・開発・QA |
| [05 データ・インターフェース設計](05-data-contracts.md) | モデル、位置表現、コマンド、IPC、状態遷移 | 開発 |
| [06 設定・保存設計](06-config-storage.md) | TOML、キーバインド、OS別配置、永続化 | 開発・運用 |
| [07 セキュリティ・障害設計](07-security-errors.md) | 信頼境界、入力制限、障害復旧 | 開発・QA |
| [08 性能・試験設計](08-performance-tests.md) | 目標、測定条件、互換性、受入ケース | 開発・QA |
| [09 技術判断・開発計画](09-decisions-roadmap.md) | 選定理由、代替案、検証ゲート、残課題 | 技術責任者 |
| [10 参考資料](10-references.md) | 公式仕様・API・配布資料 | 開発 |
初読は01→02→04→09の順、実装着手時は03・05・06・07・08を併読する。
## 設計の確度
| 区分 | 意味 |
|---|---|
| 確定要件 | 原要件、またはデスクトップGUIであるという補足合意 |
| 設計判断 | この版で採用する構成・振る舞い。変更は関連文書と試験を同時に更新する |
| 提案値 | 原要件に数値指定がないため設定した測定可能な初期値 |
| 検証ゲート | 実装開始後に実機・代表文書で確認し、未達なら選定を見直す条件 |
PDFの再現性、構造タグからの見出し移動、隔離環境の動作、縦書きEPUB、Qt依存物を含めた配布を最初に検証する。これらが未確認の段階で、全PDFへの完全互換や全Linux環境への対応を宣言しない。
## 用語
- 文書: 開いたPDF、単独HTML、HTML ZIP、またはEPUB。HTMLの内部リンク先も同じ許可ルート内なら同一文書セッションに属する。
- 目次: PDFアウトライン、EPUB nav/NCX、HTML見出しから構成したナビゲーションツリー。
- 見出し: 文書構造に記録された節の起点。PDFアウトラインとPDF構造タグは出典を区別する。
- ページ一覧: PDFの物理ページ、固定レイアウトEPUBのspine項目。流し込み文書では代わりに章一覧を使う。
- 論理位置: 再描画やウィンドウサイズ変更後も可能な限り再発見できる読書位置。
- 許可ルート: 文書セッションが参照できるファイル集合の境界。端末全体のファイル権限とは異なる。
## 文書管理
要件をR01〜R15、設計判断をADR、受入ケースをT、技術検証をGで識別する。要求の変更時は01の対応表から影響をたどる。補助的な機能は原要件と区別し、リリース範囲に入れる判断を記録する。外部資料は確認時点の情報であり、採用時にバージョンとライセンスを固定する。
+77
View File
@@ -0,0 +1,77 @@
# 01 要件・スコープ
## 製品の目的
PDFの紙面を正確に表示し、HTML・HTML ZIP・EPUBも同じ操作体系で読み進められる、Linux・Windows向けデスクトップGUIを提供する。TUI風とは、本文中心の簡潔な画面、必要時だけ現れる補助表示、Vim風のキー操作を指す。端末エミュレーター内での表示は対象としない。
## 要件一覧と設計への対応
「必須」は完成版の受入条件であり、段階的な開発順とは区別する。PDF表示を最初に成立させる。
| ID | 要件 | 優先度 | 主な設計文書 | 主な受入観点 |
|---|---|---|---|---|
| R01 | PDFを正確にレンダリングする | 最優先・必須 | 02・03・08 | 文字・画像・配置・透明・回転・注釈外観の再現 |
| R02 | ローカルHTMLを表示する | 必須 | 03・07 | HTML・CSS・画像・フォントの参照とリンク移動 |
| R03 | ZIP化HTMLを内部展開しindex.htmlと依存要素を読む | 必須 | 03・07 | 入口解決、安全な展開、相対パス・リンク |
| R04 | EPUBを表示する | 必須 | 03・08 | package/spine/nav、縦書き・RTL・固定レイアウト |
| R05 | キーボード主体・Vim風に操作する | 必須 | 04・05・06 | 開く・読む・移動・設定再読込・終了をキーで完結 |
| R06 | 見出し情報があれば見出しへジャンプする | 必須 | 03・04・05 | 元形式の構造情報から移動、情報の有無を正しく表示 |
| R07 | 目次があれば表示し、項目へジャンプする | 必須 | 03・04 | 階層・現在位置・移動先の整合 |
| R08 | 目次・ページ一覧等を選択表示し、余分なボタンやラベルを置かない | 必須 | 04・06 | 初期の最小表示、表示切替、フォーカス復帰 |
| R09 | 将来の拡張を容易にする | 必須 | 02・05・09 | 形式アダプター・コマンド・能力情報による分離 |
| R10 | キーバインドや表示設定をファイルで変更する | 必須 | 06 | スキーマ、上書き、再読込、エラー時の回復 |
| R11 | 書籍を読み込み滑らかに操作できる | 必須 | 02・08 | 初期表示、キー応答、ページ遷移、メモリの測定 |
| R12 | 少なくともLinux・Windowsで利用する | 必須 | 06・08・09 | 両OS実機で表示・入力・配布を検証 |
| R13 | 設定等をOSごとの慣習に沿って配置する | 必須 | 06 | XDG、Windows Known Folders、非ASCIIパス |
| R14 | PDFの編集・マークアップ・フォーム記入を含めない | 除外 | 03・04・07 | 保存操作やフォーム入力で原本を変更しない |
| R15 | デスクトップGUIで見た目とキー操作をTUI風にする | 確定補足 | 02・04 | GUIで正確な紙面と簡潔な操作画面を両立 |
R01〜R14の根拠は提供要件「Vim/TUI風文書リーダー」(docview.md)、R15は製品形態の補足合意である。試験ケースへの詳細対応は08に置く。
## 初期版の範囲
| 項目 | 設計範囲 |
|---|---|
| 実行環境 | Windows 11 x64、Ubuntu 24.04 LTS x64を初期の試験対象とする提案。LinuxのX11・Wayland双方で確認 |
| 文書の開き方 | OSファイル選択、コマンドラインのローカルパス、ドラッグ&ドロップ。1ウィンドウ・1アクティブ文書 |
| PDF | 静的ページ、内部・外部リンク、アウトライン、構造見出し、ページラベル、拡大縮小、回転、暗号化文書のパスワード入力 |
| HTML | ローカル静的HTMLと、同じ許可ルート内の関連HTML・CSS・画像・フォント |
| HTML ZIP | 検証済み一時領域への内部展開、index.html選択、関連資源・複数ページ |
| EPUB | DRMなしのEPUB 2/3。流し込み、固定レイアウト、日本語縦書き、RTL。詳細は03 |
| ナビゲーション | スクロール、ページ/章移動、見出し、目次、リンク、戻る/進む |
| 設定 | TOMLによるキー・配色・補助パネル・文字サイズ・性能上限 |
## 原要件を補完する設計提案
本文検索、読書位置の再開、少数の最近使った文書、エラー詳細表示、簡易ヘルプを提案する。長い書籍をキーボードで読むための補助手段として初期設計に含めるが、R01〜R15を満たすうえで独立した追加要件であり、開発計画上は削減可能である。本文検索は既存の文字情報だけを対象とし、OCRを暗黙に追加しない。
アプリの日本語UIを初期値とし、文字列を翻訳可能な資源へ分離する。読書状態・履歴はローカルに保存する。ネットワークアカウントやサーバーは必要としない。
## 表示互換性の境界
「PDFを正確に表示する」は、対応対象の文書について文字、画像、図形、クリッピング、透明度、ページ寸法・向きが読書に影響する欠落なく再現されることを意味する。代表コーパスと目視による判定を行う。異なる表示器間のアンチエイリアス差をそのまま欠陥と判定しない。
電子署名の検証、添付ファイルの実行、動画・3D・JavaScript、動的XFA、DRM解除、印刷色校正、OCRは追加機能として扱い、初期版に含めない。既存の注釈・フォームの静的外観は読書内容なので描画対象とする。検出した未対応機能や読込失敗には警告または開けない理由を示す。検出可能なXFA等は事前分類する。一方、描画APIだけですべての視覚的欠落を実行時検出できるとは想定せず、検出不能な差は代表文書の比較試験と既知制約で管理する。
HTML・EPUBの文書スクリプトと外部通信は初期版では無効とする。ローカルにそろった静的コンテンツを対応の中心とし、スクリプトや遠隔資源が必須の資料は互換性制限を表示する。EPUB規格のすべての機能への適合を宣言する設計ではない。
## 主要ユースケース
1. 文書を開く。型と権限を確認し、処理中も画面操作と取消しを受け付け、最初の本文を表示する。
2. j/k、ページ移動、倍率変更で読む。描画中のページは場所と進捗を示し、操作受付を止めない。
3. 目次を出し、階層をたどって項目に移動し、本文へフォーカスを戻す。
4. 見出し単位で進む。目次と本文見出しの出典を区別し、情報がない場合は短く通知する。
5. 設定ファイルを編集して再読込する。不正なら現在の有効設定を維持し、行・項目・理由を表示する。
6. 終了して同じ文書を再度開く。提案機能の位置復元が有効なら最後の位置へ戻る。
## 設計上の未確定事項
| 論点 | 本版の既定案 | 確定時点 |
|---|---|---|
| 配布形態・ライセンス | オープンソース依存の条件を満たす動的リンク配布を候補とする | G0・配布方式決定時 |
| 実際の書籍の種類・最大容量 | 08の代表・負荷コーパスで開始 | G0で実利用文書を追加 |
| PDF構造タグの位置精度 | 公開PDFium APIで見出し→ページ・矩形を抽出 | G0で採否判断 |
| Linuxの配布先拡大・ARM64 | 初期のx64対象とは別の検証枠 | 初期版の品質成立後 |
| 初期画面の配色 | 暗色UI・PDF原色の既定、設定で変更 | 操作レビュー時 |
これらは設計書作成を妨げる未回答事項ではない。実装時の判断を再現できるよう、既定案・判断時点・影響を記録する。
+99
View File
@@ -0,0 +1,99 @@
# 02 全体アーキテクチャ
## 採用構成
Qt 6 / C++20 / Qt Quickをアプリケーション基盤とし、PDFは独立したPDFiumワーカー、HTML・EPUBはQt WebEngineで描画する。CMakeで構成し、両OSの依存バージョンを同じリリース単位で固定する。具体的なQtパッチ版・PDFiumコミットは実装開始時に選ぶ。
Qt PDFはアプリのPDF処理には使わない。PDFiumの公開APIにアクセスする窓口を一つにし、構造タグの読取りも同じワーカーに閉じ込める。Qt WebEngine内蔵のPDFビューアは無効化する。Qt WebEngine自身の内部依存に含まれるPDFiumまで配布物から除去できるとは想定しない。
```mermaid
flowchart TB
UI[Qt Quick Reader Shell] --> APP[Application Services]
APP --> BROKER[Main Process / File Broker]
BROKER --> PDF[Sandboxed PDFWorker / PDFium]
BROKER --> ARCHIVE[Sandboxed ArchiveWorker]
BROKER --> WEB[Qt WebEngine / Isolated Content]
APP --> STORE[Config and State Store]
PDF --> UI
ARCHIVE --> BROKER
BROKER --> ROOT[Document Scoped Resources]
ROOT --> WEB
```
矢印は論理的な要求・応答を表す。PDFWorker・ArchiveWorkerは別OSプロセスであり、Qt WebEngineは独自のマルチプロセス構成を持つ。主プロセスから子プロセスを起動するだけではOSによるアクセス制限は成立しないため、07の隔離条件を別途満たす。
## 責務分割
| 層・要素 | 責務 | 保有しない責務 |
|---|---|---|
| Reader Shell | 本文領域、目次、一覧、ステータス、コマンド欄、アクセシブル名 | 形式固有の解析、任意ファイル読取り |
| Input Router | モード・フォーカス・IMEを考慮したキー解釈、コマンド生成 | PDFページ番号への直接変換 |
| Document Controller | セッション、遷移、位置、戻る/進む、エラーの統括 | 描画エンジンの内部API露出 |
| Navigation Service | アダプターの目次・見出し・リンクを共通モデルへ変換 | 見た目だけによる見出しの推測 |
| Render Scheduler | 可視域優先、先読み、取消し、キャッシュ、世代管理 | ファイルの権限判定 |
| Format Adapter | PDF/HTML/EPUB固有の能力・位置・描画の変換 | 他形式の特例をUIへ要求すること |
| File Broker | 開く操作で許可されたファイル、範囲読取り、仮想資源の提供 | PDF/XML/HTML/画像の主プロセス内解析 |
| PDFWorker | PDFium初期化、文書解析、ページ描画、文字・構造・リンク抽出 | 外部通信、原本文書の書込み |
| ArchiveWorker | ZIP検証・展開、EPUB XML解析、文書資源索引 | 任意パスへの書込み、外部実体参照 |
| Web Content Host | docスキーム、通信遮断、DOMナビゲーション、本文表示 | アプリ全体の権限や設定へのアクセス |
| Settings / State Store | 検証済み設定とローカル読書状態の保存 | 文書由来スクリプトの評価 |
依存の向きはUI→アプリケーション→抽象インターフェース。形式固有コードは抽象インターフェースを実装する。PDFium、WebEngine、OS APIはそれぞれの境界内へ閉じ込める。
## 文書を開く流れ
1. UIがOSファイル選択または明示されたローカルパスをFile Brokerへ渡す。Brokerは通常ファイル・権限・実体パス・容量を確認する。
2. 拡張子と先頭の限定的なシグネチャを照合する。ZIPとEPUBの詳細判定はArchiveWorkerで行う。HTMLは拡張子/MIMEと文字コード情報から扱い、文書本文を主プロセスで解釈しない。
3. 新しいsessionIdとgenerationを発行する。既存の文書は新文書の入口検証が済むまで保持し、失敗時に元の位置へ戻れるようにする。
4. PDFは読取り専用ハンドルまたは範囲読取りチャネルをPDFWorkerへ渡す。HTMLは選択ファイルの親ディレクトリを許可ルート候補にし、ZIP/EPUBは検証済みのセッション用資源集合を構築する。
5. 最初に必要な本文と最低限のメタデータを要求する。全ページのサムネイル、全文字抽出、全ファイルのハッシュ計算が終わるまで本文表示を待たせない。
6. 初期本文が表示できた時点でReadyへ移行し、目次・見出し・先読みを低優先で進める。準備途中の目次は「読込中」とし、「目次なし」と区別する。
7. 新文書のコミット後に旧セッションを破棄し、旧要求・共有メモリ・一時資源を解放する。旧generationの応答は採用しない。
複数文書を常駐させない。切替中の旧文書保持は一時的な例外とし、メモリ圧迫時は最後の表示画像と復帰位置だけを保つ。
## 描画・スケジューリング
PDFiumのAPI呼出しはワーカー内で直列化する。複数スレッドから無保護で呼び出さない。最初はアクティブ文書につきPDFWorker一つとし、追加並列化は性能実測後の変更とする。PDFiumのスレッド条件は[公開API](https://pdfium.googlesource.com/pdfium/+/refs/heads/main/public/fpdfview.h)を参照する。
優先度は「新しい可視ページ→可視ページの高解像度化→移動方向の隣接ページ→逆方向→サムネイル→検索索引/構造抽出」。先読みはまず前後1ページまでとし、キャッシュ余裕とキー操作から調整する。解析中にUIスレッドを同期的に待たせない。
拡大したページは512×512デバイスピクセルのタイルを基本とし、外周に2ピクセルの描画余白を持たせて表示時に切り落とす。CropBox、回転、倍率、DPRを含む変換行列をタイルとリンク・文字の当たり判定で共有する。タイルの継ぎ目・端の欠落は08の必須試験で確認する。
倍率変更中は既存画像を暫定拡大し、新しい倍率で再描画する。旧倍率の応答が新しい画面を上書きしないようgenerationとrenderRevisionを検証する。リサイズだけでは全文書を再解析しない。
PDFの段階描画を利用できる処理は短い区間で取消しを確認する。中断不能なデコードや解析はワーカー監視の期限で扱い、UI側の取消し完了とワーカーの実処理終了を同一視しない。
## キャッシュと資源管理
| 資源 | 方針 | 初期提案 |
|---|---|---|
| PDF画像タイル | バイト数基準LRU、可視タイル優先 | 256 MiB、設定可能 |
| PDFページオブジェクト | 現在・前後を保持し不要分を閉じる | 前後1ページ |
| サムネイル | パネル表示時に可視行から作る | 64 MiB以内で画像予算と調整 |
| WebEngine | 文書セッション用off-the-record profile | 永続Cookie・遠隔cacheなし |
| ZIP展開 | 安全検証済み専用ディレクトリ、一時資源 | 上限は03・07 |
| 読書状態 | 小さなJSON、画像や本文は保存しない | 06を参照 |
PDFの画像キャッシュ予算はアプリ全体のRSS上限を意味しない。WebEngine、GPU、パーサー、一時バッファは別に消費するため、08で子プロセス込みの総使用量を測る。メモリ圧迫時は先読み停止→不可視キャッシュ解放→解像度抑制の通知→文書処理停止の順で対応する。
## 拡張の契約
新形式の追加はFormatAdapterの実装、型判定の登録、能力情報、位置型の追加、共通適合試験への登録で行う。UIは能力情報を見てページ一覧・見出し・検索の可否を決める。画面内へ形式名による条件分岐を散在させない。
キーはCommand Registry内の安定したcommandIdに対応する。新しい操作は同じ入口へ登録し、既定キーがない操作もコマンド欄から実行できる。設定ファイルはスキーマに定義された値だけを受け付ける。
初期版の拡張は配布元がビルド時に組み込むモジュールに限る。第三者の任意コードを読み込むプラグイン機構は将来設計とする。将来導入する場合も、文書がプラグインや設定を自動的に選択・実行することは許可しない。
## 主な依存とビルド境界
| 部分 | 採用案 | 管理単位 |
|---|---|---|
| UI・共通基盤 | Qt Core / Gui / Quick / Qml / Quick Controls、C++20 | 同一Qtリリース |
| HTML・EPUB本文 | Qt WebEngine / WebEngineQuick | Qtと整合したChromium依存 |
| PDF | PDFium公開C API、V8/XFA無効ビルドを基本 | 固定コミット・ビルドオプション |
| ZIP・XML | 維持管理されているライブラリをArchiveWorkerへ組込み | G0でZIP64・文字コード・制限設定を確認し固定 |
| 設定 | TOML 1.0互換パーサー、独自スキーマ検証 | parser版とschema_versionを別管理 |
| IPC | 長さ付きCBORメッセージと制御された共有バッファ | protocolVersion |
ZIP・TOML等のライブラリ名は、制限付き処理・ライセンス・更新頻度を確認してG0で固定する。ライブラリ選定未了でも、受け入れるデータと制限は03・05・06・07で固定する。
+159
View File
@@ -0,0 +1,159 @@
# 03 フォーマット・取込み設計
DocView アプリケーション設計書 | 版 1.0 | 2026-09-19
対象要件: R01-R04、R06、R07、R09、R11、R14。
## 1. 対応方針
PDFを最優先とし、PDFiumを呼ぶ専用PDFWorkerで描画する。HTMLとEPUBの本文はQt WebEngineで描画する。TUI風の配色・枠線・文字中心の操作UIはアプリの領域に適用し、PDFや著者指定の本文レイアウトを変更しない。
| 形式 | 初期リリースの対応範囲 | 制約の伝え方 |
|---|---|---|
| PDF | 読取り専用表示、拡大縮小、回転、ページ一覧、outline、リンク | 対応限界・破損を文書情報とエラーで明示 |
| 単独HTML | ローカルHTMLと許可された文書フォルダー内のCSS・画像・font・リンク | 外部依存資源とスクリプト依存部分は表示できない旨を通知 |
| ZIP化HTML | `index.html` と相対参照資源を内部展開して表示 | 入口不明時は候補選択。危険なアーカイブは取込み中止 |
| EPUB 2 / 3 | DRMのない静的本文、NCX/nav、spine、縦書き、RTL、リフロー、固定レイアウト | 音声・動画・media overlays・動的対話は初期版対象外 |
静的閲覧、DRM非対応、音声・動画非対応は設計上の初期範囲であり、原要件から直接指定された制限ではない。EPUBの全機能適合を宣言するものではない。PDFの編集、注釈追加、フォーム入力、保存による文書変更は行わない。既存の可視要素の表示は、編集機能とは分けて扱う。
## 2. 共通の取込み手順
1. MainのFileBrokerが利用者の選んだファイルを読取り専用で開く。拡張子と固定長のシグネチャを候補判定に利用するが、複雑な内容解析は行わない。
2. PDFはPDFWorker、ZIP/EPUBはArchiveWorkerへ読取り専用handleを渡す。単独HTMLは許可rootと資源IDを登録する。形式の確定は担当の解析器で行う。
3. Workerが返すメタデータは、Mainでも型・長さ・件数・参照IDを検証する。Workerは信頼境界の内側ではない。
4. 新しい文書セッションに世代番号と推測困難なtokenを割り当てる。1ウィンドウのアクティブ文書は1つとする。切替候補の入口検証中は旧文書を維持し、新文書のコミット時に旧tokenと旧要求を失効させる。
5. 最初の表示位置に必要な情報・資源を優先して読み込む。目次とページ一覧は段階的に更新する。取込み・描画は取消可能とする。
6. 正常終了、文書切替、取消、失敗のすべてでhandle、共有画像、仮想URL、展開資源を解放する。画面に残っている旧文書の結果を新文書へ混ぜない。
位置とナビゲーションの形式非依存モデルは [データ・内部契約設計](05-data-contracts.md)、隔離と入力制限は [セキュリティ・異常系設計](07-security-errors.md) に定義する。
## 3. PDFの表示契約
### 3.1 正確性
PDFのページ寸法、CropBox/MediaBox、ページ回転、font、文字配置、画像、透明度、クリッピング、注釈外観を尊重する。座標はPDF文書座標を基準に保持し、device pixel ratio、ズーム、表示回転を一つの変換で適用する。CropBox左下が原点でないページ、文書のRotateと利用者の回転が重なるページも同じ変換行列で扱う。リンク矩形と文字選択の当たり判定はその逆行列を利用する。ページ本文をHTMLへ変換して表示しない。
画面は必要解像度のページ/タイル画像を使用する。倍率は論理画面上で計算し、DPRを一度だけ掛けて描画バッファ寸法を得る。低解像度の暫定画像を拡大表示している間は置換用の再描画を要求する。文字・図形の欠落、既存注釈の欠落、透明度の破綻は「表示できた」と判定しない。注釈の静的外観とフォームwidget外観は必要な描画経路を分けて確認し、編集を無効にしただけで外観も消える状態を避ける。具体的な評価方法は試験設計とPDF技術判断に従う。
未埋込みfontにはOS差が残り得る。代替fontの集合と探索順を管理し、両OSの比較対象に含める。任意PDFに対する完全一致を保証せず、検証済みの互換性範囲をリリースごとに記録する。
### 3.2 注釈・フォーム外観・フォント
通常のページ描画へFPDF_ANNOTを付けるだけではwidgetとpopupは描画されない。保存済みフォーム外観は、PDFiumのform environmentを用いるFPDF_FFLDraw系の描画経路を別途用意し、ページ本文と同じ回転・縮尺・タイル原点へ合わせて合成する。form environmentの初期化、ページ読込/破棄通知、終了処理はワーカー内で管理する。フォーム入力、action実行、JavaScript、保存APIは公開しない。タイル描画とフォーム描画の座標が一致しない場合は、可視領域単位の合成方式へ修正する。[PDFium描画API](https://pdfium.googlesource.com/pdfium/+/main/public/fpdfview.h)、[フォーム描画API](https://pdfium.googlesource.com/pdfium/+/main/public/fpdf_formfill.h)
既存外観が欠落したフォームは、暗黙にユーザー入力や文書保存で補修せず、表示できない可能性を通知する。popup内の注釈本文は選択時の読取り専用補助表示として扱い、紙面外に隠れたコメントの存在を見落とさない。静的注釈、widget、popup、appearance欠落をT18とG-RENDERで個別確認する。
埋込fontを優先し、未埋込みfontは許諾を確認した同梱候補と明示的なOS font索引を用いる。OS font索引はBrokerが管理し、PDFWorkerへは選択済みfontの読取り専用handleまたはバイト列だけを渡す。font探索目的でWorkerの任意パスアクセスを許可しない。共通font集合を固定しても、元fontが欠けた文書の完全一致を保証するものではない。
### 3.3 ナビゲーション
- PDF outlineは目次として保持し、明示destinationsとnamed destinationsをページ位置へ解決する。
- タグ構造に有効なH/H1-H6があれば見出しジャンプを提供する。矩形まで解決できれば見出し位置、ページだけ解決できる場合はページ先頭へ移動し精度を表示する。見出しからページへの対応も解決不能なら該当項目に理由を付け、全体を「見出しなし」としない。座標が得られずページだけ分かる場合はprecision=pageで移動する。有効な構造見出しがない場合は文書内宛先を持つoutlineを代用し、「目次項目移動」と表示する。目次そのものはoutlineを使用する。文字サイズだけから推測した見出しは初期版で生成しない。
- ページ番号は内部の0始まりindexと画面の1始まり表示を分ける。文書のPageLabelsがあれば併記し、`iv` や `1` と物理ページ番号を混同しない。
- ページ内リンクは現在の文書へ移動する。外部HTTP(S)リンクは宛先を提示し、利用者の明示操作で既定ブラウザーへ渡す。Launch action、JavaScript action、自動外部起動は実行しない。
- パスワードが必要な文書は専用入力を求め、値はメモリーだけに保持する。DRM等の未対応保護や未対応機能は理由を返す。
## 4. 単独HTMLとZIP化HTML
### 4.1 入口の選択
単独HTMLは選んだファイルを入口、その親ディレクトリーを初期rootとする。`../assets` 等でroot外の資源が必要な場合、許可範囲を自動拡大しない。「文書フォルダーを指定」で利用者が共通親を指定した場合だけrootを変更する。ブラウザーには絶対ローカルパスを渡さない。
ZIPの入口は次の順序で決める。
| 条件 | 動作 |
|---|---|
| root直下に完全一致の `index.html` がある | 採用 |
| rootにはなく、共通トップディレクトリー直下に `index.html` が一つある | 採用 |
| 上記以外でHTML候補がある | 相対パス一覧からキーボードで選択 |
| HTML候補がない | `E_HTML_ENTRY_MISSING` |
`index.htm`、大小文字違いの名称、複数の深いディレクトリー内のindexは候補一覧で扱い、勝手に最初の候補へ決めない。選択結果は文書fingerprintと関連付ける。ZIPでは入口が深い場所にあってもZIP全体をrootとし、正当な相対参照を維持する。
### 4.2 内部展開
ArchiveWorkerがZIP索引を解析し、安全検査後に必要資源をストリーム展開する。Mainはエントリー記述を再検証し、専用領域へのhandle単位の書込みと実バイト数の上限監視だけを担当する。ZIP/XMLパーサーをMainへ持ち込まない。
最初に入口HTMLを、その後はWebEngineから要求されたCSS、画像、font等を展開する。CRCと上限の検査を終えた資源だけを確定して公開する。展開中の不完全ファイルを描画エンジンへ渡さない。全アーカイブを一括でメモリーへ展開せず、内側のZIPも自動展開しない。
### 4.3 URLと依存資源
資源URLは `doc://<session-token>/<relative-path>` とする。`QWebEngineUrlScheme::Syntax::Host` を使い、文書ごとのhostをoriginとする。URLは当該文書のResourceBrokerだけが解決できる。
| 入力 | 解決規則 |
|---|---|
| HTMLのsrc/href/srcset | 当該HTMLのURLを基準 |
| CSSの `url()` / `@import` | 当該CSSのURLを基準 |
| font-face、SVG資源 | 対応する資源の基準URLと同じ規則 |
| `#fragment` | 現在の資源内の移動先 |
| 相対URLの `..` | URL解決後にroot内なら許可 |
| query | URL情報として保持し、OSファイル名へ直接連結しない |
| 別origin、file URL、UNC、未知のスキーム | 読込みを拒否 |
percent-encodingは一箇所で一度だけ復号する。符号化された区切り・NUL・二重復号に依存するパスを拒否し、ZIPエントリー名の `%` を索引作成時にURL復号しない。HTMLの `base` は同一文書root内に解決できるものだけ許可する。外部baseはCSPと資源gateで拒否し、影響する依存資源の欠落を通知する。黙って別の場所へ読み替えない。
HTML、CSS、SVGの解析はQt WebEngineの描画側で行う。Mainの資源ハンドラーは内容を書き換えるためのHTML/CSSパーサーを持たない。文書タイトルや目次ラベルをアプリUIへ渡す際は文字列として表示し、HTMLとして再解釈しない。
### 4.4 本文の静的閲覧
文書JavaScript、form送信、meta refreshによる自動移動、外部iframe、外部画像・CSS・fontを無効にする。ローカルの許可資源は表示する。欠落画像・fontは代替表示、CSS欠落は本文を維持した警告とする。制限の詳細は文書情報から確認できる。
一般のHTMLメニューバーを自動で目次と決め付けない。`role=doc-toc` 等の意味が明確なnavを目次として読み、それ以外はh1-h6と有効なARIA見出しから「見出し一覧」を作る。見出しがないときは情報なしを示す。文書にIDを恒久追加せず、索引側に一時識別子を持つ。
## 5. EPUB
### 5.1 packageと読書順
ZIPの安全検査後、ArchiveWorkerが `mimetype`、`META-INF/container.xml`、OPF、manifest、spineを解析する。XMLは非検証パーサーとし、外部DTD・外部実体・実体の膨張を禁止する。先頭rootfileを既定packageとし、他のrenditionの存在もメタデータに記録する。
通常の次/前移動は `linear != no` のspine順で行う。`linear=no` は本文リンクや目次から到達でき、ジャンプ履歴で元へ戻れる。manifestの未知媒体には定義済みfallbackを辿り、循環参照と深さを制限する。利用可能なfallbackがなければ該当章をエラーにし、他の章へ移動可能にする。
### 5.2 目次・章・ページの区別
| 優先順/情報 | 使用方法 |
|---|---|
| EPUB 3 navのtoc | 著者の目次として使用 |
| EPUB 2 NCX、またはEPUB 3 nav破損時のNCX | 目次として使用。破損fallback時は通知 |
| spineから生成する章一覧 | 目次がない場合の補助。「章一覧」と明示 |
| XHTMLの見出し | 見出しジャンプ用。章を開く際に段階的索引化 |
| navのpage-list | 出版物のページラベルとして保持 |
| landmarks | 補助ナビゲーションとして保持 |
著者のページラベル、固定レイアウトのページ、リフロー後の画面単位は異なる。リフロー表示では章内進捗を標準とし、画面番号を本の確定ページ番号として保存しない。navとspineの順序が違っても書き換えず、目次操作はnav、通常読書はspineに従う。
### 5.3 縦書き・RTL・固定レイアウト
CSSの `writing-mode`、`text-orientation`、`dir`、rubyを尊重する。`page-progression-direction` はページの進行方向であり、本文の文字方向と別に扱う。「次の読書位置」は論理方向で進め、h/j/k/lによる物理スクロールと分ける。
リフロー型は表示幅・文字サイズ変更後に再レイアウトし、保存した論理位置へ戻す。固定レイアウト型はXHTML viewportまたはSVG viewBoxを基準に拡縮し、本文fontサイズや行幅を強制しない。spine一項目を一ページとして扱い、見開き・RTL・項目ごとのrendition上書きを反映する。混在書籍は章の切替時に表示方式も切り替える。
### 5.4 位置の復元
保存情報はDocumentIdentityの内容識別情報と `packagePath / spineIdref / href / fragment / cfi / progression / textQuote` を基礎とする。対象packageとspineを確定した後の復元順はCFI、ID/fragment、textQuote、章内progression。CFIが壊れていても他の情報から近似復元し、近似であることを通知する。文書fingerprintが変わった場合は一致を確認してから古い位置を使う。
CFI解決と見出し抽出は、原文のDOM構造を基準にする。読書補助の要素を本文DOMへ挿入してCFIの番地を変えない。アプリが管理するUIはQt Quick側へ置く。fontや画像の読込みで位置が動く場合は、レイアウト確定後に同じ論理位置へ再配置する。縦書き・RTLのprogressionは論理読書方向へ正規化する。
### 5.5 保護と破損
`encryption.xml` があるだけでDRMと断定しない。IDPFの標準font難読化を認識して描画用に復元する。それ以外の暗号化方式、保護された本文、パスワード付きZIPは初期版非対応とし、`E_DRM_UNSUPPORTED` または `E_ARCHIVE_ENCRYPTED` を返す。
container/OPFが読めず読書順を確定できないときは開かない。nav破損はNCX・章一覧へfallbackする。spineの一部欠落は欠落章を明示して他の章を読めるようにする。正式な解析に成功していないEPUBを、ZIP内の最初のHTMLを開くだけで成功扱いにしない。
## 6. Qt WebEngineによる制御
`JavascriptEnabled=false` でMainWorldの文書スクリプトを停止する。アプリ所有の固定コードは `ApplicationWorld` に置き、DOMの見出し取得、CFI解決、表示位置測定、スクロールを行う。Qtのworld分離ではDOMへアクセスできる一方、JavaScript変数はworld間で共有されない。[QWebEngineSettings](https://doc.qt.io/qt-6/qwebenginesettings.html)、[QWebEngineScript](https://doc.qt.io/qt-6/qwebenginescript.html)
Qt Quickの `WebEngineView.runJavaScript(script, worldId, callback)` でworldを必ず指定する。文書文字列をコードへ連結せず、検証済みの型付きデータとして渡す。非同期callbackには要求ID・文書世代番号を関連付け、古い結果を捨てる。MainWorld無効化とApplicationWorld実行の組合せは、採用するQt版のG0検証で確認する。[WebEngineView](https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html)
本文WebEngineViewには汎用QWebChannelやOS APIを公開しない。設定ファイルから任意JavaScriptを実行する機能も初期版には設けない。位置・見出しの測定結果も未信頼データとしてMainで検査する。
## 7. 参照資料
確認日: 2026-09-19。規格の要点を基に、DocView独自の静的閲覧プロファイル・制限・回復動作を設計した。
- [EPUB 3.3](https://www.w3.org/TR/epub-33/) - package、spine、nav、rendition、font難読化。
- [EPUB Reading Systems 3.3](https://www.w3.org/TR/epub-rs-33/) - rootfile選択、file URL、XML処理、script fallback。
- [EPUB CFI 1.1](https://idpf.org/epub/linking/cfi/epub-cfi.html) - EPUB内の論理位置の表現。
- [QWebEngineUrlScheme](https://doc.qt.io/qt-6/qwebengineurlscheme.html) - Host構文とorigin。
- [QWebEngineUrlSchemeHandler](https://doc.qt.io/qt-6/qwebengineurlschemehandler.html) - 独自スキームの資源応答。
+232
View File
@@ -0,0 +1,232 @@
# 04 画面・操作設計
## 1. 設計方針
本文を中心にしたデスクトップGUIとし、アプリの周辺表示に等幅文字・細い区切り線・キーボード中心の操作を使う。PDFの紙面は文字端末に置き換えず、原文の色・文字・図形を保持して描画する。
R01〜R14は提供要件、R15は「デスクトップGUIで、見た目とキーボード操作をTUI風にする」という確定補足に対応する。本書の具体的なキー割当、初期レイアウト、タイムアウトは設計提案であり、利用者から指定された固定値ではない。要件一覧は01、位置・能力・コマンドのデータ契約は05、設定スキーマは06に置く。
初期版は1ウィンドウ・1アクティブ文書とする。ファイルはOSの選択画面、起動引数のローカルパス、ドラッグ&ドロップから開ける。Qt Quick側がUIとキーモードを管理し、PDFiumの表示結果とQt WebEngineの文書内容へ操作を振り分ける。文書内スクリプトにアプリのキー操作を管理させない。
## 2. 画面構成
### 2.1 初期画面
文書未選択時は、中央に「Ctrl+O: 文書を開く」「?: 操作一覧」の2項目を表示する。メニューバー、ツールバー、常時表示の装飾ボタンは置かない。OSのウィンドウ枠は通常どおり使用する。画面の余白、文字、区切り線は設定テーマの色を使う。
### 2.2 閲覧画面
```text
┌─ 目次 ─────────┬──────────────────────────────────────┐
│ 第1章 │ │
│ > 第2章 │ 原文どおりの文書描画領域 │
│ 2.1 │ │
│ 2.2 │ │
├─ ページ一覧 ────┤ │
│ 12 │ │
│ >13 │ │
│ 14 │ │
├────────────────┴──────────────────────────────────────┤
│ book.pdf 13 / 248 125% 内容 │
└───────────────────────────────────────────────────────┘
```
図はすべての領域を表示した例。初期値では**文書領域と1行のステータス行だけ**を表示し、目次とページ一覧は隠す。目次とページ一覧は独立に表示・非表示を切り替えられる。両方を表示した場合は左側の上下に配置し、片方だけなら左側の高さ全体を使う。各幅・高さの下限を確保し、小さいウィンドウではパネルを自動的に増やさない。
- **文書領域**: PDFのページは連続縦スクロールを初期値とする。ページの背景色や埋込フォントは文書どおりに描く。アプリのテーマを変更しても文書の色を勝手に反転しない。
- **目次パネル**: 項目を階層表示する。現在位置を含む最も近い項目を示す「現在位置」と、キーボードで選択中の行を示す「選択位置」を区別する。選択を動かすだけで本文を移動させない。
- **ページ一覧パネル**: PDFは実ページ番号の一覧を基本とする。ページラベルがある場合は「付録-3(実ページ 213)」のように表示できる。サムネイルは必須にせず、将来追加候補とする。HTMLには表示しない。固定レイアウトEPUBではspine項目のページ一覧、リフローEPUBではpage-listがあれば出版物由来のページ参照、なければspine由来の章一覧を表示する。見出しは「ページ一覧」「出版物のページ」「章一覧」と内容に合わせる。
- **ステータス行**: ファイル名、位置、ズーム倍率、フォーカス領域を簡潔に表示する。PDFは実ページ番号/総ページ数、HTMLは見出し名または「文書」、EPUBは章位置を表示する。HTMLやリフローEPUBに固定総ページ数を捏造しない。非表示にできる。
- **通知**: 文書読込中・エラー・利用できない操作は短い通知として表示する。通常の閲覧時に通知領域の高さを占有しない。自動消去される通知と、明示的な対処を要するエラーを区別する。
ページ一覧・目次が提供されない文書で対応キーを押した場合は「この文書にはページ一覧がありません」等を表示する。空のパネルを常設しない。マウスによるスクロール、リンク選択、パネル操作も可能とするが、主要操作はすべてキーボードで到達できる。
### 2.3 読込と異常時
1. 文書を開く操作から、OS標準のファイル選択を表示する。
2. ファイル確定後、読込状態を即座に表示する。目次や総ページ数の確定を待ってから初めて画面を更新する実装にしない。
3. 最初に必要なページ・章を表示し、他ページや目次は準備できた時点で反映する。
4. 読込が失敗した場合は、対象名、原因の分類、利用者が可能な対処を示す。内部例外・スタックトレースは画面に出さない。
5. 既存文書から別文書への切替では、新文書の最低限の表示準備が完了するまで旧文書を保持する。失敗時は旧文書へ戻る。
PDFパスワード入力等の文字入力が必要な画面は、閲覧キーを奪わない専用ダイアログにする。対応暗号方式・パスワード保持方針は03・07に従う。
## 3. 操作状態とフォーカス
Vimの通常モードに相当する閲覧操作を採用するが、文書編集用の挿入モード・ビジュアルモードは作らない。
| 状態 | 入力の扱い | 遷移・終了 |
|---|---|---|
| 内容閲覧 | 文書用キー、リンクのTab移動、マウススクロール | パネルへフォーカス移動、ファイル選択等 |
| 目次フォーカス | 行移動・階層開閉・選択確定 | Enterで本文へ移動して内容に戻る、Escで内容へ戻る |
| ページ一覧フォーカス | 行移動・選択確定 | Enterで対象へ移動して内容に戻る、Escで内容へ戻る |
| キー列待機 | `g`、`z`、`[`、`]`、`Ctrl+W`、数値カウントの後続を待つ | 完成で実行、Escで破棄、期限切れで破棄 |
| 操作一覧表示 | スクロール・閉じる操作のみ | Escまたは?で閉じて元のフォーカスへ戻る |
| コマンド入力 | `:`から入力欄を表示し、文字入力・IME・左右移動・削除を優先 | Enterで検証して実行、Escで取消して元のフォーカスへ戻る |
| ダイアログ/文字入力 | OS標準の編集操作とIMEを優先 | 確定・取消で元のフォーカスへ戻る |
| 読込中 | UIは応答し、不要になった読込の取消または別文書選択を可能にする | 成功、失敗、取消 |
### 3.1 キー処理の優先順位
1. OSまたはネイティブダイアログが占有する操作。
2. IMEの変換・確定、ダイアログ内の文字入力と標準編集操作。
3. 最前面にある操作一覧等の一時表示。
4. 現在のフォーカス領域に限定された割当。
5. アプリ共通割当。
`Ctrl+O`等の共通操作も、文字入力中・IME変換中に無条件で横取りしない。少なくとも `isComposing` 相当の状態にあるキーイベントは閲覧コマンドへ配送しない。コマンド入力、ファイル選択、パスワード入力、OSの標準ダイアログでは、`j`、`k`、数字、`?`等を通常の文字として入力できる。
### 3.2 フォーカス規則
- 文書を開き終えたときは内容にフォーカスする。
- パネルを表示しても直ちに本文位置やフォーカスを変更しない。
- フォーカス中のパネルを隠した場合は内容に戻す。
- `Ctrl+W`の後に`w`を押すと「内容→目次→ページ一覧→内容」の順に循環する。非表示領域は飛ばす。
- リンクの `Tab` / `Shift+Tab` は内容領域内のリンクを順に選択する。リンクがない場合は本文位置を変えず、内容にフォーカスを残す。パネル間の移動は専用キーで行う。
- PDF内・HTML内・EPUB内の読取専用文書要素は、アプリの通常操作モードを勝手に切り替えない。フォーム記入や文書内スクリプトによるキーフックは設けない。
- フォーカス枠、選択行、現在位置は色だけに依存せず、線・記号・濃淡で区別する。
## 4. 既定キーバインド案
キー表記は論理キー名を使う。Shiftを要する大文字は別のキーとして扱い、日本語配列/英語配列で文字を入力する物理キーが異なっても、設定上は `?`、`+` 等の文字で識別する。OSが予約するキーは上書きできない場合があるため、設定チェックと操作一覧で知らせる。
### 4.1 内容領域
| キー | 動作 | 境界・備考 |
|---|---|---|
| `j` / `k` | 小さく下/上へスクロール | 設定可能な移動量。キーリピートに対応 |
| `h` / `l` | 横方向にスクロール | 横の余りがない場合は何もしない |
| `Ctrl+D` / `Ctrl+U` | 表示領域の半分だけ進む/戻る | PDFは下/上。HTML/EPUBは書字方向に沿った読書軸を使う |
| `Ctrl+F` / `Ctrl+B` | 表示領域のほぼ1画面分進む/戻る | 読書軸に沿って移動し、少量の重なりを残す |
| `gg` / `G` | 文書の先頭/末尾へ | EPUBは読書順序の先頭/末尾。パネルでは先頭/末行 |
| `数字G` | PDFの指定実ページへ | 例: `42G`。HTML/EPUBでは利用不可と通知 |
| `J` / `K` | 次/前のPDF実ページへ | PDFのみ。移動先ページの先頭へ |
| `]]` / `[[` | 次/前の見出しへ | 見出し情報がある場合のみ |
| `]c` / `[c` | 次/前のEPUB章へ | EPUBのspine順に移動。非linear項目は通常順送りから除外 |
| `+` / `-` | 拡大/縮小 | PDFは表示倍率、HTML/リフローEPUBはズーム。原文は変更しない |
| `=` | 幅に合わせる | PDFまたは固定レイアウトに適用。リフロー形式では標準倍率へ戻す |
| `Tab` / `Shift+Tab` | 次/前のリンクを選ぶ | リンク数が多い場合もスクロール位置を選択先へ追従 |
| `Enter` | 選択リンクを実行 | 文書内移動・別文書/外部リンクの扱いは基盤仕様に従う |
| `Esc` | 未完成のキー列またはリンク選択を解除。読込・索引待機中は要求を取り消す | 文字入力/IMEの取消を優先し、文書自体は閉じない |
このキー案では `Ctrl+F` をVim流の画面送りに使う。追加提案である本文検索は `/` で開始する。原要件を満たす基本操作と、検索・位置復元等の補助機能を区別する。
### 4.2 目次・ページ一覧
| キー | 目次 | ページ一覧 |
|---|---|---|
| `j` / `k` | 次/前の可視行 | 次/前の行 |
| `h` | 展開中なら折りたたみ、そうでなければ親へ | 何もしない |
| `l` | 子があれば展開、展開済みなら最初の子へ | 何もしない |
| `gg` / `G` | 先頭/末尾の可視行 | 先頭/末尾の行 |
| `Ctrl+D` / `Ctrl+U` | 半画面分の行を移動 | 同左 |
| `Enter` | 選択項目へ移動 | 選択ページへ移動 |
| `Esc` | 内容へフォーカスを戻す | 同左 |
### 4.3 共通操作
| キー | 動作 |
|---|---|
| `Ctrl+O` | 文書を開く |
| `t` | 目次の表示/非表示を切り替える |
| `p` | ページ一覧の表示/非表示を切り替える |
| `zs` | ステータス行の表示/非表示を切り替える |
| `Ctrl+W` `w` | 表示中の領域間でフォーカスを移す |
| `:` | コマンド入力欄を表示する |
| `?` | 現在の設定を反映した操作一覧を表示する |
`t`、`p`、`J`、`K`には文書リーダー独自の役割を割り当てる。Vimの完全互換ではないことを操作一覧で示す。`q`単押しで終了する操作は誤操作を避けて既定に含めず、OS標準のウィンドウ終了を使う。
### 4.4 キー列と設定検証
- 接頭辞待機の既定値は800 msとする設計提案。06に従い200〜3,000 msへ変更でき、0は無期限待機とする。
- 単一キーと、そのキーを接頭辞とする長いキー列を同じ有効コンテキストへ割り当てた場合はエラーにする。例: `g` と `gg` の併存は不可。
- 同じ有効コンテキストで同じキーを複数動作へ割り当てる設定は拒否する。共通割当と領域別割当で同じキー列がある場合は領域別を優先する。利用者設定は(mode, context, keys)が一致する既定定義を置換する。異なる長さの接頭辞衝突は、同時に有効なcontext間でも拒否する。
- 設定変更はファイル全体の構文・値・キー衝突検証が成功した後、一括で適用する。失敗時は最後に有効だった設定を維持し、行番号と修正可能な理由を表示する。
- 数字は内容領域のPDFページ指定にのみ使い、先行ゼロを除き1以上の整数に制限する。入力中の数字は必要時だけ通知する。末尾が `G` 以外または期限切れなら入力列を破棄して、直前キーを別のコマンドとして勝手に実行しない。
- フォーカス変更、文書切替、ダイアログ表示時は未完成キー列を破棄する。
- リピートイベントで接頭辞待機を何重にも作らない。`j`等の連続スクロールはリピートを受け付けるが、パネル表示切替は長押しで点滅しない。
## 5. 形式別の位置と移動
### 5.1 PDF
- 内部のページ番号は0始まり、利用者向けの実ページ番号は1始まりとし、変換箇所を位置型の境界へ限定する。
- `42G` はラベル「42」ではなく42枚目を開く。ページラベルと実ページが異なる場合は、ステータスと一覧に両方を表示する。
- 現在ページは、文書表示領域の中央線に交わるページを基本とする。中央線がページ間の余白にある場合は近いページ、同距離なら前のページとする。これにより、ページ端で次/前操作が不安定になることを避ける。
- 次/前ページは現在ページ番号に±1を適用し、移動先ページの上端へ移動する。末尾・先頭ではその位置にとどまり、短く境界を通知する。
- PDFの目次は文書内のアウトライン情報を利用する。構造タグのH/H1〜H6から有効なページ位置を取得できる場合は、これを見出しジャンプへ用いる。構造タグの有効な見出しがない場合は、文書内宛先を持つアウトラインを代わりに用い、表示名を「目次項目移動」とする。構造タグ由来の見出しとアウトライン由来の項目は共通位置型へ変換しても、情報源の種別を保存する。両方を単純に併合して同一の項目へ二度移動させない。文字サイズ推定・OCRによる見出し生成は行わない。
- ズーム変更時は、表示領域の中心にある文書上の点をできる限り保持する。異なる大きさや回転を持つページへ移動しても、そのページ固有の幾何情報を尊重する。
### 5.2 HTMLとZIP HTML
- 通常HTMLはそのファイルを入口とする。ZIP HTMLは現在表示するHTMLを基準に相対URLを解決する。CSS内の参照はそのCSS自身の場所を基準とする。入口が複数ある場合・存在しない場合の選択規則は基盤仕様に従う。
- `h1`〜`h6` と有効なARIA見出しをDOM順で収集して見出し移動を提供する。`display:none`等で表示されない要素は対象外とする。見出し文言が空の場合は「見出し(レベルN)」と表示する。
- HTMLの目次は、明示的な文書内目次のリンク構造が認識可能な場合にそれを採用する。認識不能でも見出しから作成した**見出し一覧**を利用できる。この一覧を著者作成の目次と混同しないようにラベルを区別する。
- ZIP内の別HTMLへのリンクは同じ出版物コンテキストで開く。ページ一覧をファイル一覧として流用しない。HTMLを開いた順を「章順」として推定しない。
- `gg` / `G` は現在のHTMLリソースの先頭/末尾である。ZIP全体の先頭/末尾とは解釈しない。
- リフロー・ズーム変更後は、先頭可視要素とその要素内の相対位置を基準に位置を回復する。DOMに安定した位置がない場合はスクロール比率へフォールバックする。
### 5.3 EPUB
- 章の通常移動はパッケージのspine順に従う。目次の表示順から章の読書順を推定しない。
- リフロー形式は表示領域と作者の書字方向に従って組版する。横書きの通常文書は縦スクロール、日本語縦書き等はwriting-modeとdirectionに合った読書軸を使う。`j`/`k`と`h`/`l`は物理方向、半画面/1画面移動は読書方向に対応する。次/前章は次/前の通常読書対象spine項目の先頭へ移動する。
- 目次はEPUB 3のnavigation document、EPUB 2はNCXを利用する。両方ある場合の優先順位はEPUB基盤仕様で固定する。
- EPUB内の `h1`〜`h6` と有効なARIA見出しを見出しジャンプの対象とする。章境界を越える見出し移動は、次/前の通常読書対象spine項目の最初/最後の有効見出しへ移る。先読みは必要最小限とし、全章を描画してから読書開始する構成にしない。
- 章単位の位置と章内アンカーを持ち、画面幅の変更でページ数が変わっても同じ箇所に留まる。EPUB page-listのページ番号は書籍が提供した参照であり、現在の画面分割数ではない。
- 固定レイアウトはそのviewportと読み方向を尊重する。日本語縦書き・RTLを含め、作者のCSSと出版物の方向情報を使う。固定レイアウトとリフロー文書では倍率・幅合わせの意味を分ける。
### 5.4 見出し移動の共通定義
「次の見出し」は現在の読書位置より後ろにある最初の有効な見出し、「前の見出し」は現在の読書位置より前にある最後の有効な見出しとする。同一の目的地へ重複する見出しは一度にまとめる。見出しの先頭ぴったりにいる場合、その見出しを再選択しない。PDFで見出しのページだけが確定し座標が確定しない場合は、`precision=page`としてページ先頭へ移動し、その精度を表示する。確定していない位置を正確な見出し位置と扱わない。目的地が不正な目次項目は選択時に理由を示し、他の目次項目まで使えなくしない。
## 6. コマンド契約と文字入力
設定ファイル、キー入力、コマンド入力は同じコマンドIDへ解決する。コマンド入力欄は必要時だけ最下部に現れ、ステータス行が非表示でも利用できる。入力中は等幅文字で `:` と文字列、エラーがあればその理由を示す。
| ID | 既定キー/文字列 | 引数・適用対象 |
|---|---|---|
| `file.open` | `Ctrl+O`、`:open` | 引数なしはファイル選択。`:open "path"` は指定ローカルパスを開く |
| `app.quit` | `:q` | 終了。OS標準の終了操作も使用可 |
| `operation.cancel` | 読込/索引待機中の`Esc`、`:cancel` | 現在の待機要求を取り消す。新文書のコミット前なら旧文書へ戻る。文字入力/IMEの取消が優先 |
| `document.root.choose` | `:root` | 単独HTMLの許可フォルダーをOSの選択画面で指定。新root内へ資源を再解決 |
| `document.info` | `:info` | 形式、検出した制限、欠落資源、エラー詳細を表示 |
| `history.clear` | `:history clear` | 保存した履歴・位置を件数確認後に消去。補助機能 |
| `scroll.down` / `scroll.up` | `j` / `k` | 内容領域の縦スクロール |
| `scroll.left` / `scroll.right` | `h` / `l` | 内容領域の横スクロール |
| `scroll.half_down` / `scroll.half_up` | `Ctrl+D` / `Ctrl+U` | 表示領域の半分 |
| `scroll.page_down` / `scroll.page_up` | `Ctrl+F` / `Ctrl+B` | 表示領域のほぼ全体 |
| `nav.document_start` / `nav.document_end` | `gg` / `G` | 形式別に定める文書範囲 |
| `nav.page` | `数字G`、`:page N` | 1始まりのPDF実ページ番号。範囲外はエラー、暗黙に末尾へ丸めない |
| `nav.page_next` / `nav.page_previous` | `J` / `K` | PDFのみ |
| `nav.heading_next` / `nav.heading_previous` | `]]` / `[[` | 有効な見出しまたは代替目次項目がある文書 |
| `nav.chapter_next` / `nav.chapter_previous` | `]c` / `[c` | EPUBのみ |
| `panel.toc.toggle` | `t`、`:toctoggle` | 目次または見出し一覧の表示を切り替える |
| `panel.pages.toggle` | `p`、`:pagestoggle` | 利用可能なページ/章一覧の表示を切り替える |
| `panel.status.toggle` | `zs`、`:statusbar` | ステータス行の表示を切り替える |
| `focus.next` | `Ctrl+W` `w` | 表示中の領域を循環 |
| `zoom.in` / `zoom.out` | `+` / `-` | 現在位置を保持して拡大/縮小 |
| `zoom.fit_width` | `=` | 固定ページでは幅合わせ、リフローでは標準倍率 |
| `view.rotate` | `:rotate 90`、`:rotate -90` | PDFの表示回転のみ。原本へ保存しない |
| `command.open` | `:` | コマンド入力状態へ移行 |
| `config.reload` | `:reload` | 06で定義する検証と一括再読込 |
| `help.toggle` | `?`、`:help` | 現在のキー設定に対応する操作一覧 |
| `nav.back` / `nav.forward` | `Alt+Left` / `Alt+Right` | 同一文書コンテキスト内の明示的なジャンプ履歴 |
| `search.open` | `/` | 本文検索の入力欄。補助機能の提案 |
| `search.next` / `search.previous` | `n` / `N` | 検索結果。補助機能の提案 |
`nav.back` / `nav.forward` は目次・リンク・指定ページ等のジャンプを戻す。小さなスクロールのすべてを履歴へ積まない。新しい文書を開くことと、同じZIP内HTMLへの移動は、文書コンテキストの境界によって区別する。
コマンドの引数はアプリ内パーサーで処理し、シェルへ渡さない。パスは空白を含む場合に二重引用符で囲み、Windowsのバックスラッシュは文字どおり扱う。シェル変数、ワイルドカード、`!`による外部コマンド実行は展開しない。未知のコマンド、引数不足、余分な引数は入力欄に理由を表示し、文書状態を変更しない。IME確定のEnterはコマンド実行と兼用せず、変換確定後のEnterで実行する。
## 7. 補助機能の提案
本文検索、読書位置の再開、最近使った文書は原要件の独立した追加提案である。実装順の判断により削減できる。これらを省略してもR01〜R15の試験を省略しない。
- **本文検索**: `/`で検索欄を開く。入力中はIMEと文字編集を優先する。Enterで検索し、`n`/`N`で結果を移動する。PDFの既存テキストとHTML/EPUBの表示テキストを対象とし、画像へOCRを適用しない。検索処理は取消可能にし、結果なしと文字情報なしを区別する。
- **読書位置の再開**: 文書を閉じる前の論理位置・倍率をローカル保存し、同じ文書を開いたときに回復する。原文の変更やウィンドウ幅の違いによる復元精度の低下を考慮し、保存先と識別方式は05・06に従う。HTML/リフローEPUBの画面上のページ数を永続キーにしない。
- **最近使った文書**: 初期画面の追加表示として設定で有効化できる。初期画面を履歴の大量表示で埋めない。消去機能と保存上限を持ち、原本を変更しない。
## 8. UI実装前の確認項目
Qt QuickとQt WebEngineの間のフォーカス、IMEのcompositionイベント、日本語/英語キーボード配列、Wayland/X11、Windowsの表示スケール差を実機で確認する。PDFのアウトラインと構造タグ由来の見出し位置は出典を区別して保持する。これらは実装開始時の技術検証対象とする。
+128
View File
@@ -0,0 +1,128 @@
# 05 データ・インターフェース設計
## 設計原則
文書の位置は形式ごとの意味を保持した直和型で表現する。内部のページ番号は0始まり、画面の物理ページ番号は1始まりとする。ページラベルは文字列であり数値として計算しない。ドメインモデルはQtやPDFiumのオブジェクトを直接含めず、非同期操作の全応答に文書の世代を付ける。
## 主要モデル
| モデル | 主なフィールド | 制約・意味 |
|---|---|---|
| DocumentIdentity | documentId、canonicalPath、fileIdentity、size、mtime、contentHash? | documentIdは端末内UUID。物理ファイルIDは利用可能時のみ。ハッシュ未計算を許容 |
| DocumentSession | sessionId、generation、format、state、capabilities | sessionIdは起動ごとの予測困難な識別子。保存IDとは別 |
| DocumentMetadata | title、author?、language?、pageCount?、spineCount? | 未判明と0を区別。文字列長を制限しUIへプレーンテキスト表示 |
| PageInfo | pageIndex、label?、widthPt、heightPt、cropBox、rotation | PDFの元座標と表示変換を保持 |
| TocNode | id、parentId?、title、target?、source、children | sourceはpdf-outline / epub-nav / epub-ncx / html-toc / html-heading / spine |
| Heading | id、level?、title、target、source、precision | level不明可。precisionはexact / page / approximate |
| Link | region?、target、label?、kind | kindはinternal / external / blocked。文字列をコマンドとして実行しない |
| ViewState | location、zoomMode、zoomValue、rotation、panels、focus | ズームはpercent / fit-width / fit-page。本文フォントサイズとは別 |
| ReaderState | documentId、savedLocation、updatedAt、formatVersion | 文書が変わった場合は位置の再解決が必要 |
配列は必要に応じページングして応答する。巨大な目次や数万項目を一度にUIへ投入しない。titleの空白・制御文字を整理する際も原本は変更しない。
## 位置の表現
| 型 | 保存する値 | 再解決 |
|---|---|---|
| PdfLocation | pageIndex、pageLabel?、xPt、yPt、viewRotation、fitMode | PDFユーザー空間の位置をCropBox・回転・DPRで画面へ変換。変更文書ではページ範囲を検証 |
| HtmlLocation | resourcePath、fragment?、domPath?、textQuote?、progression | fragment→DOMと文字列照合→資源内進捗の順。progressionは0〜1 |
| EpubLocation | packagePath、spineIdref、href、cfi?、fragment?、textQuote?、progression | パッケージ内のspine→CFI→fragment→文字列→進捗の順で解決 |
PDFのxPt/yPtは元PDF座標系を明示して保存し、画面左上基準のCSS座標とは混ぜない。常に変換行列と逆行列を使う。CropBox外の指定は最寄りの可視領域へ調整し、精度をpageまたはapproximateとして通知する。
HTML/EPUBのスクロール軸はwriting-modeとdirectionから決める。progressionは読み進めた割合であり、scrollTop/scrollHeightだけでは定義しない。書体や幅が変わっても文字列アンカーを先に試す。レイアウト完了前の仮サイズから復元位置を確定しない。
textQuoteは短い前後文脈を持つが保存を無効化できる。ローカル状態には文書パスと短い本文断片が含まれ得るため、06の履歴消去対象に含める。スクリプト、DOMオブジェクト、任意XPath評価式は保存しない。
## 能力情報
能力はsupported / unavailable / loading / unsupportedの4状態を基本とし、単純な真偽値で「未取得」と「存在しない」を同一視しない。
| 能力 | 意味 |
|---|---|
| pageNavigation | 安定した物理ページに移動できる |
| chapterNavigation | spineや複数HTML資源に沿った移動がある |
| outline | 文書に目次として使える構造がある |
| headings | 本文または構造情報から節の起点を得られる |
| textSearch | 既存文字情報を検索できる。画像PDFではunavailableになる場合がある |
| textSelection | 内容が文字選択を許容し、テキストを取得できる |
| reflow | 本文の文字サイズ・行幅変更を反映できる |
| password | 現在パスワードを要求している |
unsupportedには理由コードを付ける。UIは理由をヘルプ/一時通知に出し、無効ボタンを常時並べない。
## FormatAdapterの論理契約
以下は実装言語に依存しないインターフェース定義であり、アプリケーションコードではない。操作は非同期、sessionIdと取消しトークンを受け取る。
| 操作 | 入力 | 結果 | 特記事項 |
|---|---|---|---|
| probe | 制限付きファイル情報・先頭バイト | format、confidence、理由 | ZIP内部の詳しい判定は隔離側 |
| open | DocumentSource、policy、password? | session、metadata、capabilities | パスワード再入力と破損を区別 |
| getOutline | cursor、limit | TocNodeの部分集合、nextCursor | 同一文書で安定したID |
| getHeadings | resource/page範囲、cursor | Headingの部分集合、完了状態 | 抽出途中のジャンプ要求は待機/取消し可能 |
| resolveLocation | 保存位置、復元方針 | 解決済み位置、precision | 失敗時は先頭へ黙って移さず通知 |
| navigate | target、alignment | 到達位置、renderRevision | jump履歴への記録はControllerが担当 |
| renderPdfTiles | page、matrix、clip、revision | TileBatch | PDF専用描画ポート。WebEngineは可視域通知ポートを使う |
| setViewport | size、DPR、readingStyle | layoutRevision | reflow後に位置再解決 |
| findText | query、direction、cursor、limit | match位置、nextCursor | 提案機能。平文検索が既定 |
| close | sessionId | released | 繰返し呼出しは無害 |
共通契約は操作の意図をそろえるためのもので、PDFの画像タイルとHTMLのDOMを同一の描画データへ変換しない。
## コマンドと入力の境界
Command RegistryはcommandId、引数スキーマ、実行可能モード、必要な能力、取消し可否を持つ。Input Routerからはキー名そのものではなく検証済みCommandを受け取る。マウス操作も同じCommandを経由する。
コマンド入力欄は定義済み文法だけを解釈する。シェル、外部コマンド、JavaScriptの実行機能は持たない。パスの引用符は構文として扱うが、変数展開・コマンド置換・グロブ実行はしない。`:open`のパスは明示操作による新しい許可対象としてBrokerで検証する。
## プロセス間メッセージ
IPCは長さ付きCBORフレームをローカル専用チャネルで送る。外部待受ポートを開かない。子プロセスの標準出力を信頼済みのコマンドストリームとして解釈しない。実装時にパイプ/ローカルソケットを選び、OSごとのアクセス制御を適用する。
| フィールド | 内容 |
|---|---|
| protocolVersion | 整数。未知版は接続を拒否し依存不整合を報告 |
| requestId | セッション内一意、応答・取消しの対応づけ |
| sessionId / generation | 文書と世代。旧文書の応答を破棄 |
| operation | ホワイトリスト列挙。任意メソッド名を実行しない |
| payload | 操作別の有限スキーマ。整数範囲・文字列長・配列数を検証 |
| result / error | どちらか一方。errorはコード、再試行可否、利用者向け説明キー |
制御フレームの上限は1 MiB、PDFタイル/資源データは別の分割チャネルで1片8 MiB以下を提案する。1タイルの最大寸法・stride・ピクセル形式・バッファ長を受信側で再検証し、乗算の桁あふれを拒否する。数値は08で測定し、07の入力制限と合わせて変更する。
共有メモリを使う場合はBrokerが割り当てた短命のバッファIDだけをワーカーへ渡し、サイズ上限・所有セッション・読書き方向を管理する。ワーカーが示す任意パスやハンドル値を主プロセスの権限で開かない。close/取消し/クラッシュ時に参照数を解放し、解放済み応答を表示しない。
## 状態と遷移
```mermaid
stateDiagram-v2
[*] --> Empty
Empty --> Opening: open
Ready --> Opening: open another
Opening --> PasswordRequired: encrypted
PasswordRequired --> Opening: submit
Opening --> Ready: initial content
Opening --> Failed: invalid or unsupported
Ready --> Recovering: worker crash
Recovering --> Ready: explicit retry
Recovering --> Failed: retry fails
Ready --> Closing: close
Closing --> Empty: resources released
```
OpeningとPasswordRequiredの取消しは、旧文書があればReadyへ戻し、なければEmptyへ戻す。図のFailedはエラー状態を表し、元文書を保持している場合はエラー表示後に旧Readyへ復帰できる。終了要求はどの状態からでも受け付ける。
ReadyにはnavigationIndex=loadingという独立した副状態を持てる。スクロール・ズームのたびにOpeningへ戻さない。パネル表示は文書状態と独立し、読み込み途中に目次を開いても競合しない。
## 整合性と取消し
- 文書切替でgeneration、倍率や向きの変更でrenderRevision、HTML/EPUBの再配置でlayoutRevisionを進める。
- PDFium処理は直列キューとし、取消し要求は予約済み処理を除去する。実行中の処理は安全な区切りで止める。
- closeは読取チャネルの失効→新規要求停止→実行処理取消し→ワーカー終了→一時資源回収の順。停止不能時は期限付きで強制終了する。
- 読書位置の保存は最後に画面へ反映された位置だけを対象とする。未到達のジャンプ要求で保存位置を更新しない。
- 戻る/進む履歴は明示ジャンプとリンク移動を単位に記録する。連続スクロールの全フレームは記録しない。
## エラーの責務
ワーカーは技術的な原因と位置を返し、Controllerは回復操作へ翻訳する。UIへOSの例外やスタックトレースをそのまま表示しない。利用者が必要とするファイル名・ページ・理由は表示し、ログ側ではパスや本文を原則省く。コード体系と回復策は07を正とする。
+150
View File
@@ -0,0 +1,150 @@
# 06 設定・保存設計
## 設定方式
利用者が編集する設定はUTF-8のTOMLとし、`config.toml`一つを入口にする。初期設定の全項目を記載しなくても、差分だけで上書きできる。`schema_version = 1`で構造の版を識別する。設定から任意のコードやシェルを実行しない。
優先順位は「組込み既定値 < 利用者設定 < 起動引数で明示した一時上書き」。`--config PATH`は読む利用者設定ファイルを置き換え、二つの利用者設定を暗黙に合成しない。起動引数はファイルパス、設定パス、初期ページ等の限定された項目だけを許可する。読書状態は表示位置の復元にのみ使い、キーや配色の設定より優先させない。
文書と同じディレクトリにある設定やZIP内部の設定は自動読込みしない。ファイルを開くだけでキーや許可範囲が変更されることを防ぐ。
## OSごとの配置
| 種類 | Linux | Windows |
|---|---|---|
| 設定 | `$XDG_CONFIG_HOME/docview/config.toml` | `%APPDATA%\DocView\config.toml` |
| 読書状態・履歴 | `$XDG_STATE_HOME/docview/state.json` | `%LOCALAPPDATA%\DocView\state\state.json` |
| キャッシュ | `$XDG_CACHE_HOME/docview/` | `%LOCALAPPDATA%\DocView\cache\` |
| ログ | `$XDG_STATE_HOME/docview/logs/` | `%LOCALAPPDATA%\DocView\logs\` |
| ZIP等の作業領域 | cache配下の`sessions/<sessionId>/` | cache配下の`sessions\<sessionId>\` |
Linuxの未設定/空のXDG変数は順に`~/.config`、`~/.local/state`、`~/.cache`へフォールバックする。相対パスのXDG値は仕様に従い無効とする。Windowsは文字列置換した環境変数だけに頼らず、Known Folders APIでRoamingAppDataとLocalAppDataを解決する。QtのQStandardPathsは共通の経路解決を補助するが、AppConfigLocationのWindows既定はLocal側であるため、上記Roaming側設定と混同しない。[XDG仕様](https://specifications.freedesktop.org/basedir/latest/)、[QStandardPaths](https://doc.qt.io/qt-6/qstandardpaths.html)、[Windows Known Folders](https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid)を参照。
すべてユーザー専用領域とし、Linuxではディレクトリ0700・状態ファイル0600相当、Windowsでは現在ユーザー中心のACLを継承・確認する。インストール先、実行ファイル隣、現在ディレクトリへ暗黙に設定を書かない。
## 設定項目
数値は初期提案であり、08の性能測定に合わせて調整する。セキュリティの絶対上限・隔離の有効化は利用者設定で解除できない。
| キー | 型・既定値 | 範囲/効果 |
|---|---|---|
| schema_version | integer = 1 | 未知の将来版は適用しない |
| ui.theme | string = "dark" | dark / light / system |
| ui.status_bar | boolean = true | 状態1行を表示。zsで一時切替 |
| ui.toc_visible | boolean = false | 初期の目次表示 |
| ui.pages_visible | boolean = false | 初期のページ/章一覧表示 |
| ui.font_size | number = 14 | 10〜28論理px、補助UIの文字サイズ |
| ui.panel_width | integer = 280 | 160〜600論理px、画面幅でも上限を制約 |
| pdf.zoom_mode | string = "fit-width" | fit-width / fit-page / percent |
| pdf.zoom_percent | integer = 100 | 25〜800、percent時のみ使用 |
| pdf.preserve_colors | boolean = true | 本文の原色を維持。falseは表示フィルターを使う追加提案 |
| reading.font_size | number = 18 | 12〜40論理px、流し込みHTML/EPUBの利用者表示設定 |
| reading.line_height | number = 1.6 | 1.0〜2.5。著者指定維持との切替を持つ |
| reading.use_publisher_style | boolean = true | 既定は著者スタイル。利用者上書き時もwriting-mode等を保持 |
| keys.sequence_timeout_ms | integer = 800 | 0(無期限)または200〜3000、複数打鍵の待機。IME合成中は解釈しない |
| performance.tile_cache_mib | integer = 256 | 64〜512、PDFタイル予算。総RSS制限ではない |
| performance.prefetch_pages | integer = 1 | 0〜3、前後ページ。メモリ圧迫時は自動抑制 |
| privacy.remember_position | boolean = true | 読書位置を保存する提案機能 |
| privacy.recent_documents | integer = 20 | 0〜100、0で最近使った一覧を無効化 |
| privacy.store_text_anchor | boolean = false | 本文断片の永続保存。無効でもCFI/fragment等で復元可能 |
`pdf.preserve_colors=false`の表示フィルターは原色を変えるため、正確な再現性の受入はtrueで行う。初期実装でフィルターを採用しない場合、falseを未対応として検証エラーにし、黙って受け付けない。
## 設定例
以下は設計上の設定例であり、アプリケーションのプログラムではない。通常は変更した項目だけを記述する。
```toml
schema_version = 1
[ui]
theme = "dark"
status_bar = true
toc_visible = false
pages_visible = false
font_size = 14
panel_width = 280
[pdf]
zoom_mode = "fit-width"
zoom_percent = 100
preserve_colors = true
[reading]
use_publisher_style = true
font_size = 18
line_height = 1.6
[keys]
sequence_timeout_ms = 800
[performance]
tile_cache_mib = 256
prefetch_pages = 1
[privacy]
remember_position = true
recent_documents = 20
store_text_anchor = false
[[keybindings]]
mode = "normal"
context = "content"
keys = ["j"]
command = "scroll.down"
[[keybindings]]
mode = "normal"
context = "global"
keys = ["t"]
command = "panel.toc.toggle"
[[keybindings]]
mode = "normal"
context = "content"
keys = ["]", "]"]
command = "nav.heading_next"
```
## キー設定の仕様
1. 一つのキーは`j`、`J`、`Ctrl+D`、`Alt+Left`、`Esc`のような標準表記とする。大文字と小文字を区別し、印字キーはキーボード配列上の文字として解釈する。物理スキャンコードへの依存は初期版で導入しない。
2. `keys`配列は順番に押すキーを表す。`["g", "g"]`、`["Ctrl+W", "w"]`のように記述する。修飾キーの同時押しは一要素に含める。
3. `mode`はnormal / command / search / dialogとし、入力欄で合成中のIMEイベントは対応付けより優先する。`context`はglobal / content / toc / pagesとする。commandとsearchの文字入力はユーザーキー設定で奪わない。
4. 適用キーは(mode, context, keys)で同定する。利用者設定の同じ組は既定定義を一つ置換する。既定を無効化する場合は`command = "unbound"`とする。同じファイルに同じ組が二つあれば検証エラーとする。
5. 現在のパネルcontextを先に、globalを次に評価する。通常モードのtはどのパネルからも目次を切り替えるが、文字入力中のtは入力文字になる。
6. `g`と`gg`のように実行コマンドが互いに前方一致する定義は、同時に有効になるcontext間も含めて拒否する。既定のgは未実行の接頭辞なのでggと共存できる。部分列の待機中に不一致のキーが来たら接頭辞を破棄し、新しいキーを一度だけ再評価する。ただし数字Gの入力中は不一致キーも消費し、意図しない操作を起こさない。
7. Escは複数打鍵・検索・コマンド・一時モードを抜ける回復手段として予約し、再割当て不可とする。OSが予約するキーやファイル選択ダイアログの標準操作はアプリが横取りしない。
8. コマンドID、引数、必要能力を検証する。存在しないIDは保存成功として扱わない。現在の文書で能力がない操作は理由を短く表示する。
数値付きページ移動`42G`はInput Routerの限定文法として扱い、数字全般の自由なキー再割当てとは衝突させない。詳細なキー一覧・文法・フォーカス動作は04を正とする。
## 再読込みとエラー
設定は起動時と`:reload`時に読み込む。初期版はファイル監視による自動適用を行わず、保存途中の内容による揺れを防ぐ。最大設定サイズは1 MiB、入れ子深度やキー数にも上限を設ける。
読込み→TOML解析→型と値域→キー衝突→コマンド能力→適用計画の順に検証する。全検証が成功した場合だけ設定スナップショットを一括交換する。失敗した場合は直前の有効設定を保持し、行番号・キー・修正理由を表示する。初回起動なら組込み既定値を使う。
未知のキーは綴り間違いを見逃さないためエラーとする。将来版のschema_versionは読取り専用の説明を表示し、利用者ファイルを自動で書き換えない。移行が必要な場合は変換案とバックアップを用意し、明示操作で保存する。
配色・パネル・キー・キャッシュ予算は即時反映し、再配置を伴う文字サイズは現在の論理位置を保持して反映する。適用中はキー列を取消し、二重実行を防ぐ。再起動が必要な項目を将来追加するときは、効果発生時点をスキーマに明示する。
## 読書状態と履歴
状態は`state.json`へ保存し、`state_version`、文書ID、ファイル識別情報、最終位置、倍率、更新時刻を持つ。アプリ設定は自動で書き換えない。パネルの一時切替はセッション内状態であり、次回起動はconfigの既定へ戻す。
元ファイルの完全ハッシュは初期表示を妨げない低優先処理にする。最初は正規化したパス・サイズ・更新時刻・利用可能なファイルIDで候補を探す。文書の同一性が確認できない場合は「以前の位置を復元できない」とし、似たファイルへ位置を自動転用しない。ファイルが変更された場合は保存アンカーを再検証し、曖昧なら位置選択を提示する。
位置は停止後2秒のdebounceと最大10秒間隔を提案し、正常終了時にも保存する。急な終了では直前10秒程度の位置更新を失う可能性がある。保存先と同じディレクトリ内の一時ファイルへ書き、flush後に原子的な置換を使う。前回正常版を1世代だけ残し、破損時はバックアップへ戻す。
アプリはユーザーごとに一つの状態Writerを持つ。二重起動は既存インスタンスへファイルオープン要求を送る。IPCは同じユーザーのプロセスだけに制限する。ロックが得られない場合は読書専用モードで起動し、設定・状態を競合上書きしない。
`:history clear`は最近使った一覧、保存位置、textQuote、状態バックアップを消去する提案コマンドとする。実行前に対象件数を表示し、文書原本は含めない。`privacy.remember_position=false`は将来の保存を止める設定であり、既存データの消去とは分ける。
## 一時データとログ
ZIP/EPUB作業領域はsessionIdごとに分離し、正常終了で削除する。起動時は自分の未使用セッション領域だけを回収し、他インスタンスが使用中の領域を削除しない。削除対象の実体・所有者・ロックを確認し、シンボリックリンクを追跡しない。
ログは日時、エラーコード、形式、処理時間、匿名の要求IDを中心とし、原則として本文・パスワード・完全パスを記録しない。ローカルに5 MiB×3世代を提案する。外部送信は行わない。詳細診断を保存する機能を将来設ける場合も、利用者が内容を確認して書き出す方式とする。
キャッシュは削除しても原本文書と設定に影響しない。履歴や設定の破損で本文が開けなくならないよう、保存処理のエラーは表示処理から切り離す。
+166
View File
@@ -0,0 +1,166 @@
# 07 セキュリティ・異常系設計
DocView アプリケーション設計書 | 版 1.0 | 2026-09-19
対象要件: R01-R04、R09、R11、R12、R14。文書を開く操作が、原本の変更、文書外のファイル読取り、外部送信、任意プログラム実行へ広がらないようにする。
## 1. 信頼境界
ローカルにあるPDF、HTML、ZIP、EPUBも未信頼入力として扱う。文書の名前・本文・メタデータに書かれた指示をアプリの命令や設定として解釈しない。文書処理から設定ファイルを読み替えたり、拡張コードを自動ロードしたりしない。
| 領域 | 許可する処理 | 禁止する処理 |
|---|---|---|
| Main / FileBroker | UI、検証済み設定、利用者が開いたファイルのhandle管理、限定範囲のIO、IPC検証 | PDF、ZIP、EPUB XML、HTML、CSS、画像の内容解析 |
| PDFWorker | 渡されたPDFの解析・描画、限定的なfont資源利用 | ネットワーク、任意ファイル、原本への書込み、外部起動 |
| ArchiveWorker | 渡されたZIPの索引・展開、EPUB構造XML、font難読化の処理 | 任意パス作成、ネットワーク、XML外部実体、外部起動 |
| WebEngine文書側 | 文書セッション内資源の表示、アプリ管理のDOM位置操作 | 設定・履歴・任意OS API、別文書、遠隔通信、文書JavaScript |
MainがWorkerから受けた索引・ラベル・URL・画像を無条件に信用すると境界が失われる。型、長さ、整数範囲、参照先、文書世代番号を検査し、描画画像についてもstride、幅、高さ、共有バッファ長を照合する。制御IPCの上限は1 MiB、データチャネルは1チャンク8 MiBを初期値とし、詳細は [データ・内部契約設計](05-data-contracts.md) に従う。
## 2. OSによる隔離と配布条件
別プロセスに分けただけではファイルやネットワークへのアクセスを防げない。PDFWorkerとArchiveWorkerにはOSによる制限を設定する。実装前のG0では起動可能性だけでなく、境界の外にある試験ファイルへのアクセス拒否、通信拒否、子プロセス起動拒否、資源上限超過時の停止を実証する。未達の対象OS版は配布しない。
| OS/実行系 | 採用方針 | G0で確かめること |
|---|---|---|
| Linux Worker | seccompとLandlockを組み合わせる。要件を満たせない環境は同等の隔離launcherを利用 | 必要ABI・syscall、font読込み、handle受渡し、全threadへの制限、socket・process作成・root外IOの拒否 |
| Windows Worker | AppContainerを権限境界とし、Job Objectで寿命・資源・子プロセスを管理 | network capabilityなし、必要handleだけの継承、font依存、DACL、停止・再起動 |
| Qt WebEngine | Qt/Chromiumのsandboxを有効のまま利用 | 両OSの配布形態、実際のrenderer隔離、必要なLinux kernel機能、無効化フラグの検知 |
Landlock単独で全種類の通信制限が揃うとは想定せず、socket等の操作はseccomp側でも制限する。Job Object単独もファイル権限の代替にしない。Workerの許可資源は読取り専用文書handle、限定IPC、必要なfont/runtimeのみとし、ホーム全体や任意ディレクトリーを許可しない。[Linux Landlock](https://docs.kernel.org/userspace-api/landlock.html)、[AppContainerの起動](https://learn.microsoft.com/en-us/windows/win32/secauthz/implementing-an-appcontainer)
`--no-sandbox`、`QTWEBENGINE_DISABLE_SANDBOX` 等で保護を外す運用を製品の回避策にしない。sandboxが必要条件を満たさない場合は `E_SANDBOX_UNAVAILABLE` を表示し、該当形式を開かない。Qt WebEngineはrendererのsandboxを提供するが、独立したPDFWorkerやArchiveWorkerの隔離までは提供しない。[Qt WebEngine Platform Notes](https://doc.qt.io/qt-6/qtwebengine-platform-notes.html)
## 3. ZIP・一時資源
### 3.1 検証と書込み
ArchiveWorkerは展開前に索引を検査し、Mainも公開対象entryを再検証する。Mainは検証済みの相対名を文書rootのdirectory handleに結び付け、OSのNOFOLLOW相当の手段でファイルを開く。文字列の前方一致だけでroot内と判定しない。Workerはディスク上の任意展開先を指定できない。
- 絶対パス、ドライブ名、UNC、NUL、`..` セグメント、バックスラッシュ、ADSを作るコロン、Windowsデバイス名を拒否する。
- symlink、hardlink、junction/reparse point、device、FIFOを拒否する。通常ファイルとディレクトリーだけを受け入れる。
- 正規化後の重複、大小文字・Unicode正規化による衝突、ファイルとディレクトリーの衝突、末尾のドット/空白で曖昧になる名前を拒否する。
- アーカイブの所有権、実行権限、ACLを再現しない。ユーザー専用の新しい一時ディレクトリーを作り、他セッションの内容を混ぜない。
- 申告サイズだけでなく、Mainへ届いた実展開バイト数を計測する。各entryのCRC・長さ・完了状態が正常になるまで仮想URLから公開しない。
- 内側のZIPは再帰展開しない。エラー時に展開途中ファイルを残して再利用しない。
これらはZIP Slip、リンクを使う脱出、解凍による資源枯渇を別々に防ぐための方針である。[OWASPアーカイブ試験](https://wstg.owasp.org/latest/4-Web_Application_Security_Testing/10-Business_Logic/09-Upload_of_Malicious_Files/)
### 3.2 上限の初期提案
数値は原要件にはなく、代表書籍と負荷試験で妥当性を確認する設計値である。初期版では06の利用者設定による緩和を提供せず、ビルド時の有限上限とする。値の変更時は安全試験を再実施し、パス・リンクの検査そのものは無効化できない。
| 上限対象 | 初期値 | 判定する場所 |
|---|---:|---|
| ZIP entry数 | 20,000 | Worker索引、Main索引受信 |
| 1資源の実展開量 | 256 MiB | WorkerとMainストリーム受信 |
| 1文書の実展開量 | 2 GiB | WorkerとMainセッション合計 |
| path階層 | 64 | WorkerとMain |
| pathのUTF-8長 | 1,024 bytes | WorkerとMain |
| 膨張率 | 200倍。実展開量32 MiB超のentryに適用 | Workerの入力消費量/出力量 |
| EPUB構造XML 1資源 | 16 MiB | Worker |
| XML深さ/要素数 | 128/200,000 | Worker |
| 同時資源展開 | 2 | スケジューラー |
| 1資源の展開時間 | 10秒で継続表示、30秒で取消 | Main watchdog |
| PDFの1要求 | 30秒で長時間処理を通知、120秒でワーカー停止 | Main watchdog。操作取消は待たずに受理 |
| PDF/Archiveワーカーのメモリー | 1プロセスあたり1.5 GiBを初期の停止上限とする | OS制限とMain監視。通常時の全体1 GiB目標とは別 |
絶対サイズを主要防御とし、圧縮率だけで判定しない。CPU・メモリーはWorkerおよびWebEngineの資源監視でも制限する。WebEngineでの画像デコード前に全ピクセル数を安全に判定できるとは想定せず、rendererの応答監視とメモリー超過時の停止を併用する。WebEngineの停止閾値は初期1.5 GiB/rendererを提案し、監視間隔による瞬間的な超過はあり得る。展開先の空き容量不足は上限超過と別のエラーにする。上限を変更した版での再試行時も入口から同じ検査を行う。
終了時はそのセッションの管理領域だけを削除する。次回起動時の残骸清掃は、所有マーカーと管理下の実体を確認して行う。文書由来のパスを再帰削除の起点にしない。
## 4. HTML・EPUBの隔離
### 4.1 独自URLと資源提供
`doc://<session-token>/...` を `Syntax::Host` で起動初期に登録し、QQuickWebEngineProfileへ専用scheme handlerを入れる。文書ごとのoff-the-record profileを使い、Cookie、localStorage、WebEngine履歴を文書間で継承しない。文書を閉じるとprofileとtokenを破棄する。
`LocalAccessAllowed`、`ContentSecurityPolicyIgnored`、`ServiceWorkersAllowed`、`CorsEnabled`、`FetchApiAllowed` は付けない。`NoAccessAllowed` によって全資源が別originになる構成も使わず、同一文書内のCSS/font等の互換性を保つ。安全なschemeフラグだけに依存せず、handlerはセッションtoken・initiator・method・相対パス・resource kindを照合する。
許可methodは読取要求のみ。空initiatorはアプリが発行した既知のトップレベル要求に限定して受け付ける。文書から別tokenを当てられても、tokenの一致と許可資源集合の両方で拒否する。文書URLのtokenを長期履歴やログへ残さない。
### 4.2 3箇所の制御
| 制御点 | 役割 |
|---|---|
| QWebEngineUrlRequestInterceptor | Chromiumのネットワーク層に届く前に、同一文書の許可資源以外を拒否 |
| QWebEngineUrlSchemeHandler | 実体root・許可entry・MIME・サイズ・寿命を検査し、読取専用資源を応答 |
| WebEngineView.navigationRequested / newWindowRequested | トップレベル遷移、自動遷移、外部ウィンドウ作成を制御 |
request interceptor内で展開完了を同期的に待たない。許可判定を短時間で終え、資源の非同期応答をhandlerへ委ねる。欠落や取消ではrequest jobを失敗として終了し、jobの破棄に合わせてQIODeviceの寿命も終える。
外部HTTP(S)リンクはユーザーが選択したときだけMainへ通知し、宛先を表示したうえで「外部ブラウザーで開く」操作によりOSへ渡す。WebEngine内で外部サイトへ遷移しない。未知のスキーム、OSコマンド、メール送信、file URLを一般のURLとして起動しない。
### 4.3 本文スクリプトとCSP
MainWorldのJavaScriptを無効にし、ApplicationWorldのアプリ固定コードだけで読書位置・見出しを制御する。汎用WebChannelを公開せず、結果は限定されたcallbackから返す。document内の文字列をJavaScriptソースへ埋め込む方法は使わない。
scheme handlerの追加応答ヘッダーでCSPを設定する。基本方針は `default-src 'none'`、`script-src 'none'`、`connect-src 'none'`、`object-src 'none'`、`frame-src 'none'`、`form-action 'none'`。画像・fontは同一文書内、CSSは同一文書内と著者のinline styleを許可する。画像のdata URLはimage用途に限定して許可し、top-level documentやSVG scriptの抜け道にしない。`base-uri 'self'` とナビゲーションgateを組み合わせる。
応答ヘッダーのAPIはQt 6.6以降に存在する。CSPが独自schemeとApplicationWorldで意図どおり動くことをG0で確認し、CSPを迂回するフラグを付けない。CSPを差し込むためにMainで本文HTMLを解析・改変する設計は採らない。[QWebEngineUrlRequestJob](https://doc.qt.io/qt-6/qwebengineurlrequestjob.html)
カメラ、マイク、位置、通知、screen capture、clipboard、file picker、download、protocol登録は文書からの要求を拒否する。DNS prefetch、link auditing、local-file access、local-to-remote access、WebGL、plugins、WebEngine内蔵PDF viewerを無効にする。WebEngineが要求する権限は既定拒否とし、document内の設定で上書きしない。
## 5. PDFに固有の制約
PDFiumはV8とXFAを無効にした構成を基本とする。JavaScript action、Launch action、添付実行、外部自動起動は提供しない。ファイルは読取り専用で開き、PDF保存APIをアプリの操作として公開しない。既存の注釈・フォーム値の静的外観は描画するが、文書へ入力しない。
暗号化PDFのパスワードはその場の入力だけで受け取り、設定・履歴・ログ・クラッシュ情報に残さない。明示的な再入力を許し、誤りを破損ファイルと混同しない。署名が見えるPDFでも、署名検証を実施したと表示しない。
ワーカーへ渡すfontは許可集合に限定する。未埋込みfontに対応するためホーム全体を読めるようにしない。悪意あるfont、巨大画像、再帰的なPDF構造もPDFWorkerの時間・メモリー制限で扱う。
## 6. エラーの共通モデル
エラーは `code / severity / operation / documentGeneration / resourceId / retryable / userMessage / diagnosticId` を持つ。本文や秘密を含む生データをmessageに連結しない。短い説明と復帰操作を主表示とし、詳細は必要時に開く。繰返し発生する欠落資源は件数をまとめ、キー操作を通知で奪わない。
| ID | 表示と復帰 |
|---|---|
| E_OPEN_FAILED | ファイルを開けない。場所・権限を確認し別ファイルを選択 |
| E_FORMAT_UNSUPPORTED | 対応形式でない、またはシグネチャ不一致。元文書へ戻る |
| E_PASSWORD_REQUIRED | PDFパスワードを入力。取消で元文書へ戻る |
| E_PASSWORD_INVALID | パスワードが一致しない。再入力可能 |
| E_PDF_CORRUPT | PDFを解析できない。元文書へ戻る |
| E_PDF_FEATURE_UNSUPPORTED | 動的XFA等の未対応内容。静的部分が読める場合も制限を通知 |
| E_SANDBOX_UNAVAILABLE | 安全な文書処理環境を作れない。該当形式の読込みを停止 |
| E_ARCHIVE_UNSAFE_PATH | root外へ出るパス・リンク・衝突を検出。取込みを停止 |
| E_ARCHIVE_LIMIT | 件数・実展開量・深さ等の上限。項目と上限を提示 |
| E_ARCHIVE_CORRUPT | 索引・長さ・CRCが破損。取込みを停止 |
| E_ARCHIVE_ENCRYPTED | パスワード付きZIPは非対応 |
| E_HTML_ENTRY_MISSING | HTMLの入口がない。入口候補があれば選択 |
| E_RESOURCE_MISSING | 一部資源がない。代替表示で読書を継続 |
| E_RESOURCE_BLOCKED | root外・遠隔資源等を遮断。読める部分を維持 |
| E_EPUB_PACKAGE_INVALID | package/読書順が確定できない。読込みを停止 |
| E_EPUB_NAV_INVALID | nav破損。NCXまたは章一覧へfallback |
| E_EPUB_SPINE_MISSING | 該当章が欠落。他の章へ移動可能 |
| E_DRM_UNSUPPORTED | 未対応の暗号化・保護方式。解除処理は行わない |
| E_LOCATOR_UNRESOLVED | 正確な位置に戻れない。近い位置へ戻ったことを通知 |
| E_WORKER_TIMEOUT | 処理期限超過。取消/再読込み/別文書 |
| E_WORKER_CRASH | Worker停止。UIを維持し、利用者の明示操作で再試行。繰返す停止ではセッションを閉じる |
| E_STORAGE_FULL | 一時領域・状態保存先の空き不足。展開を止めて清掃 |
| E_CONFIG_INVALID | 設定エラー。現在の有効設定を維持 |
| E_STATE_SAVE | 読書状態を保存できない。閲覧を継続し保存失敗を通知 |
ファイル切替中の失敗は、コミット前なら元文書と元位置を維持する。操作取消は通常結果として扱い、エラー通知を出さない。深刻な境界違反や繰返すクラッシュでは再試行ループに入らず、その文書セッションを閉じる。
## 7. 診断情報・更新
ログは時刻、エラーID、操作、件数、処理時間、依存版を中心とする。本文、パスワード、URL token、任意のローカル絶対パスを既定ログへ記録しない。必要なファイル名も利用者の診断操作で確認できる範囲に留める。クラッシュレポートや利用統計を自動送信しない。
Qt、Chromium、PDFium、ZIP/XMLライブラリーの依存一覧と版をリリースごとに残す。脆弱性修正時は内容解析器を優先して更新し、PDF描画の回帰と文書隔離試験を行う。更新で無効になった保護を性能改善の名目で省略しない。
## 8. 必須の境界試験
受入試験に、path traversal、symlink、大小文字衝突、ZIP bomb、偽の申告サイズ、CRC不一致、XML実体、CSS外部import、外部font、script、iframe、form、meta refresh、file URL、別token、二重percent、Worker偽応答、サイズ不正画像を含める。
期待結果はUIが落ちないことに加えて、許可外ファイルの読取り・書込み・送信・プログラム起動が発生しないこと。G0では監視用ファイル・通信先を用いて観測する。通常のCSS相対参照やIDPF難読化fontも同時に試験し、防御が正当な書籍を不必要に壊さないことを確認する。
## 9. 参照資料
確認日: 2026-09-19。制限値と配布ゲートはDocView独自の設計判断である。
- [QWebEngineUrlRequestInterceptor](https://doc.qt.io/qt-6/qwebengineurlrequestinterceptor.html) - ネットワーク層より前の要求制御。
- [QQuickWebEngineProfile](https://doc.qt.io/qt-6/qquickwebengineprofile.html) - 文書用profileとhandler登録。
- [QWebEngineUrlScheme](https://doc.qt.io/qt-6/qwebengineurlscheme.html) - originと権限フラグ。
- [QWebEngineSettings](https://doc.qt.io/qt-6/qwebenginesettings.html) - 文書スクリプト・local資源等の設定。
- [WebEngineView](https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html) - 遷移・権限・renderer停止の通知。
- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html) - 展開後サイズの制限。
+128
View File
@@ -0,0 +1,128 @@
# 08 性能・受入試験設計
## 1. 検証方針
PDFの正確な表示を最初の品質判断とし、その後にキーボード操作、HTML/ZIP/EPUB、設定、Linux/Windowsでの動作を確認する。要件IDは01に対応する。本書は試験計画であり、アプリケーションの実装、性能計測、各試験の合格を報告するものではない。
数値は未測定の性能予算である。最低環境、実利用文書、PDFium・Qt WebEngineの組合せを固定して測定し、目標変更が必要な場合は09の意思決定記録へ理由と影響を残す。
## 2. 受入試験
### 2.1 試験の読み方
以下は試験の**設計**であり、合格済みの結果ではない。PDFを優先して実装・検証するが、全要件に対応したリリースの完了判断にはHTML/ZIP/EPUBと両OSの試験が必要となる。
試験データは再配布権を確認し、ファイルのハッシュ、出典、フォント条件、期待されるページ数・リンク先を固定する。PDF描画の正解画像は評価対象と同じ描画エンジンから作らず、独立した既知の表示環境または原版から用意し、その作成環境を記録する。
### 2.2 要件対応試験表
| 試験ID | 要件 | 入力・操作 | 合格条件 |
|---|---|---|---|
| T01 | R01 | 埋込フォントあり/なし、日本語・縦書き、ベクター、透明合成、画像、各種ページ寸法、回転、CropBoxを含むPDFを開く | 期待する文字・図形・画像が欠落/置換/不正配置されず、ページ寸法・回転・可視範囲が正しい。指定倍率・両OSで確認 |
| T02 | R01 | PDFの1ページ目、途中、最終ページへ移動し、ズームを変更する | 正しいページを描画し、古いページの描画結果が現在ページへ混入しない。位置保持規則を満たす |
| T03 | R01, R11 | 数千ページのPDFを開いて末尾へ直接移動する | 全ページの描画を待たずに要求ページを処理する。UIが応答し、不要な要求を取り消せる |
| T04 | R02 | 相対CSS、画像、文書内アンカー、別HTMLリンクを含むローカルHTMLを開く | 仕様で対応対象の資源を表示し、相対参照とアンカーが正しく解決される |
| T05 | R03 | ディレクトリ構造を持つZIP内index.htmlからCSS・画像・別HTMLへ進む | アーカイブ内で参照が解決し、リンク先が正しく表示される。入口不在/複数時は定義した結果になる |
| T06 | R04 | EPUB 2/3の目次順とspine順が異なる本、縦書き/RTL、固定レイアウトを開く | 次/前章はspine、目次は目次宛先に従う。章境界、nonlinear項目、書字方向、固定viewportが仕様どおり |
| T07 | R05, R15 | マウスなしで開く、スクロール、指定PDFページ移動、目次選択、パネル切替、別文書を開く | すべて実施でき、入力待ちとフォーカスが分かる |
| T08 | R05 | 同じキーを内容、目次、ページ一覧、文字入力の各状態で入力する | 状態表どおりに振り分けられ、文字入力中に閲覧位置が変わらない |
| T09 | R05, R12 | 日本語IME変換中にj/k、数字、Enter、Escを入力し、日英配列で記号キーを操作する | IME操作が閲覧コマンドにならない。日英配列で設定された論理キーを入力できる |
| T10 | R05, R10 | キー列の期限切れ、Esc、フォーカス変更、接頭辞の長押しを試す | 未完のキー列が残らず、予期せぬ操作・連続した表示切替が起きない |
| T11 | R06 | PDFの構造見出しのみ/アウトラインのみ/双方あり/双方なし、HTML/EPUBの見出し、同宛先の重複見出しで次/前を操作する | 出典と位置を区別し、04の優先順位で移動する。見出しなしは明示し、推測の見出しを生成しない |
| T12 | R07 | 多階層目次を展開/折りたたみ、項目を選択する | 選択だけでは本文を動かさず、確定時に正しい宛先へ移動する。不正な宛先でも他項目を操作できる |
| T13 | R08 | 目次・ページ一覧・ステータスを個別に切り替える | 個別に反映し、フォーカス中のパネルを隠すと内容へ戻る。隠れた領域が空白として残らない |
| T14 | R08, R15 | 明暗テーマ、100%/200%表示スケールでPDFを表示する | アプリ外観のみテーマが変わり、PDF原文の色を変更しない。主要操作・文字が欠けない |
| T15 | R10 | 有効なキー設定と表示設定を書き、再読込する | 全体の検証後に一括適用し、操作一覧も新設定になる |
| T16 | R10 | 構文誤り、型誤り、範囲外値、重複/接頭辞衝突のキー設定を読む | 行/項目と理由を知らせ、直前の有効な設定を維持する。部分適用しない |
| T17 | R12, R13 | LinuxとWindowsの初回起動、非ASCIIユーザー名、パスに空白、読み取り専用設定ディレクトリで起動する | 各OSの配置規則を満たし、パスを破損しない。書けない場合でも利用可能な既定設定で開始し、対処可能な通知を示す |
| T18 | R14 | 静的注釈・popup・widget・保存済みフォーム外観・外観欠落を持つPDFを表示して操作し、終了する | 文書に既にある内容は対応描画範囲に従って表示するが、編集・入力・保存を提供しない。原本ハッシュが変わらない |
| T19 | R09 | 設計レビューで別形式アダプターの追加手順と依存関係を追跡する | UI固有処理へパーサーを埋め込まず、文書能力・位置・ナビゲーション契約で接続できる。採否は設計レビュー記録で残す |
| T20 | R11 | 次節の代表文書と操作列を両OSで測定する | 合意された測定条件と性能予算を満たす。実測値・分位値・失敗数を記録する |
| T21 | R01-R04, R05 | 破損、読めないファイル、必要資源欠損、読込途中でEscを押す、または別文書を開く | 明確なエラーまたは部分表示の状態を示す。クラッシュせず、旧要求の完了が新文書を上書きしない |
### 2.3 PDF描画の判定
PDFの正確性を単一の画素一致率だけで判定しない。アンチエイリアスや色管理の違いがあるため、少なくとも以下を確認する。
1. ページ数、ページ寸法、回転、切り抜き範囲の一致。
2. 文字・グリフの欠落、豆腐表示、順序違い、画像欠損、合成順序違いがないこと。
3. 正解画像との重ね合わせによる図形・文字位置と可視領域の一致。
4. 透明・マスク・グラデーション・色空間など、PDFエンジン依存箇所の目視比較。
5. 低倍率・標準倍率・高倍率および表示スケール差の確認。
差分画像は検出補助として保存し、許容できる縁の差と本文の欠落を分けて判定する。画素差の閾値は代表コーパスで校正してから確定する。現時点で「99%一致なら正確」等の数値を保証値として置かない。
## 3. 性能測定設計
### 3.1 指標と暫定目標
以下の数値は、実測前の**提案する性能予算**であり、現在の達成値ではない。基盤選定時の試作測定で達成可能性を確認し、製品の最低動作環境とともに確定する。
| 指標 | 起点と終点 | 標準コーパスでの暫定予算 |
|---|---|---|
| 入力への視覚応答 | キーイベント受領→移動/選択/読込中表示が画面へ提示される | p95 ≤ 50 ms |
| 開く操作後の最初の内容表示 | ファイル選択確定→最初に読む内容を判読できるフレーム提示 | アプリキャッシュなしp95: PDF 2 s、HTML 2 s、ZIP HTML 5 s、EPUB 5 s |
| キャッシュ済みPDFページ移動 | ページ移動キー受領→対象ページの最終品質フレーム提示 | p95 ≤ 100 ms |
| 未キャッシュPDFページ移動 | ページ移動キー受領→対象ページの最終品質フレーム提示 | 標準コーパスp95 ≤ 500 ms |
| 継続スクロール | 同一の操作列のフレーム間隔を取得する | 60 Hz環境でp95 ≤ 33 msを目安とする |
| プロセス群のメモリ | アプリ・PDFワーカー・WebEngine等を含むRSS合計 | 標準コーパスの通常閲覧で1 GiB以下を目標とする。厳密なハード上限ではない |
| キャッシュメモリ | 文書描画キャッシュの実使用量 | 設定上限を超えない。プロセス群のRSSとは別に記録 |
PDFを開く際に小さいプレビューだけを先に描く設計なら、「最初に判読可能な表示」と「最終品質到達」を別々に測る。HTML/EPUBでは最初の文字だけを出してスタイル適用を遅らせた状態を完了にしない。必要なCSSと表示範囲内の主要な内容が反映された時点を記録する。
### 3.2 コーパスの構成
| 区分 | 代表例 | 目的 |
|---|---|---|
| 標準PDF | 100 MiB以下/500ページ以下の代表群、文字中心、画像混在、埋込日本語フォント | 日常の読書操作と開く時間 |
| 重いPDF | 1 GiB/5,000ページ規模の代表群、大きいスキャン画像、複雑な透明/ベクター | 長時間停止を防ぐ処理、キャッシュと取消 |
| HTML | 100 MiB以内の参照資源群を持つ長い見出し構造、CSS、ローカル画像 | レイアウト変更と見出し移動 |
| ZIP HTML | 圧縮時100 MiB以下、展開後500 MiB以下、複数階層の相対リンク、画像資源が多い書籍 | 展開・入口解決・内部リンク |
| EPUB | 100 MiB以下/500 spine項目以下、画像混在、日本語、章数が多い例 | spine移動、章の切替、位置保持 |
これらのページ数や容量は試験セットの選定目安であり、それだけで入力上限を決定しない。アーカイブ展開量等の安全上限はセキュリティ仕様と整合させる。極端な文書には標準コーパスと同じ表示時間を一律に保証せず、応答・取消・資源上限の維持を優先して別枠で評価する。実際に採用した各ファイルのハッシュと特性を試験報告に固定する。
### 3.3 再現可能な測定手順
1. 暫定の基準機は物理4コア相当のCPU、メモリ8 GiB、SSD、内蔵GPU、1,920×1,080/60 Hzとする。LinuxとWindowsの対象バージョン、CPU型番、コア数、メモリ、ストレージ、GPU、ドライバー、表示解像度/倍率、リフレッシュレート、アプリと描画エンジンのバージョンを記録する。同じ構成の比較を基本にする。
2. 冷起動はアプリのキャッシュなしで測る。OSファイルキャッシュを消していない場合は、その事実を明記して「完全コールド」と呼ばない。アプリを開き直すだけの試験と区別する。
3. 読込試験は文書ごと30回を目安とし、p50/p95、最小/最大、失敗数を報告する。p95の安定性が不足する場合は追加測定する。
4. 操作試験は固定したページ/見出し/章の移動列を最低100操作実施し、キャッシュヒットとミスを分離する。入力時刻と実際の画面提示時刻を計測し、要求を送っただけの時刻で完了にしない。
5. キー長押し、遠いページへの連続ジャンプ、ウィンドウのリサイズ、別文書への切替を含め、古い仕事を破棄した回数、描画待ちの数、UI停止時間も記録する。
6. メモリはアプリ単体と補助プロセスを含むプロセス群を分けて測る。開始時、最大、文書閉鎖後の使用量を記録し、同じ文書を10回開閉して無制限増加しないか確認する。OSのRSSが直ちに基準値へ戻らないことだけでリークと断定しない。
7. 最低動作環境の正式決定後、その環境で受入判定する。高性能な開発機だけの達成を製品保証としない。
## 4. 補助機能の試験
以下は01・04の追加提案を採用した場合の受入条件である。必須要件の合否と区別する。
| 試験ID | 対象 | 合格条件 |
|---|---|---|
| T-P01 | 本文検索 | 日本語/英語で検索でき、`n`/`N`で正しい位置へ移動する。画像PDFは文字情報なしを示し、勝手にOCRを開始しない |
| T-P02 | 検索中の入力 | IME変換・Enter/Escが閲覧操作にならない。検索取消後に古い結果が新しい文書へ反映されない |
| T-P03 | 位置復元 | 同じ文書の再開位置が復元される。リフロー時は論理位置を使い、原本が変化した場合は不正な位置を適用しない |
| T-P04 | 最近使った文書 | 保存上限・無効化・履歴消去が機能する。削除済みの原本は理由を示し、別ファイルとして誤って開かない |
## 5. 異常系・非機能の追加確認
07の安全境界の設計が実際に有効であることを、通常文書の試験とは独立して確認する。以下の拒否は仕様に基づく挙動であり、アプリケーションのクラッシュを合格としない。
| 試験ID | 対象 | 合格条件 |
|---|---|---|
| T-S01 | ZIPの絶対パス・親ディレクトリ参照・シンボリックリンク・展開量超過 | 07の規則で拒否し、許可ルート外へファイルを書かない。中断した一時領域を回収する |
| T-S02 | HTML/EPUBの外部URL・文書スクリプト・フォーム・ローカルルート外参照 | 07の通信/参照規則を守る。文書から任意のファイルやOS機能へ到達できない |
| T-S03 | PDFワーカーの異常終了・固まった読込・不正な応答 | UIは応答し、対象文書の失敗を示す。古いワーカー応答を新文書へ適用しない |
| T-S04 | データ消去・一時ファイル回収 | 読書履歴等の対象を区別して消去し、原本を消さない。異常終了後の一時領域も起動時の規則で回収する |
| T-S05 | 配布物からの初回起動 | WindowsとLinuxの対象環境で依存ライブラリ、サンドボックス、WebEngine資源、ライセンス同梱が成立する |
## 6. 合否の記録と完了条件
試験報告には、試験ID、要件ID、対象ビルド、OS/表示環境、文書ハッシュ、手順、期待結果、実結果、証拠画像または計測記録、判定、既知制限、再試験条件を記載する。未実施と失敗を区別し、将来機能の保留を必須要件の合格に置き換えない。
完成版の受入条件は次のとおり。
1. R01〜R15に対応するT01〜T21が、対象OSで合格している。PDF描画の対応範囲は03と一致し、検出可能な未対応要素を通知する。実行時に検出できない描画差はコーパスの比較試験で判定する。
2. T-S01〜T-S05の隔離・入出力・回復・配布確認が完了している。
3. 合意された基準機・コーパスで性能予算を達成している。未達項目があれば、制限と判断を記録して受入判断を行う。黙って目標値を実績値へ書き換えない。
4. 補助機能を採用した場合は対応するT-P系列の試験が完了している。
5. 残っている問題が、原文の欠落、データ破損、キー操作不能、原本書換え、UI長時間停止を引き起こさない。
+95
View File
@@ -0,0 +1,95 @@
# 09 技術選定・設計判断・実装ロードマップ
対象: DocView(仮称)/設計日: 2026-09-19/状態: 設計基準案
## 1. 採用構成
**Qt 6 / C++20 / Qt Quick を用いたデスクトップ GUI とし、PDF は独立プロセスの PDFium、HTML と EPUB は Qt WebEngine で表示する。** UI は TUI 風の簡潔な外観と Vim 風キーボード操作を備える。今回の成果物は設計書であり、アプリケーションの実装・試作・性能測定は含まない。
Qt、PDFium、WebEngine、ZIP ライブラリ、フォント、ビルドツールの具体的な版は、実装開始時にサポート状況と検証結果を確認して固定する。PDFium は公開 C API を使用し、取得元、コミット、ビルドオプション、成果物のハッシュを記録する。最新版へ無条件に追従せず、修正版の評価と回帰試験を伴って更新する。
## 2. 候補比較
下表の評価は本アプリケーションの要件に対する設計判断であり、各製品の公式な優劣評価ではない。描画精度の順位を示す比較試験は未実施である。
| 候補 | 適合する点 | 本件で負担となる点 | 判断 |
|---|---|---|---|
| Electron + PDF.js | Web 技術による UI、HTML、EPUB の統合が容易。PDF.js を個別更新できる | Electron / Chromium / Node.js / PDF.js の更新と権限境界を管理する。PDF 互換性は別途検証が必要 | 代替候補として保存 |
| Qt Quick + Qt PDF + Qt WebEngine | Qt PDF の目次、リンク、検索、ビューア部品を利用できる | 確認した Qt PDF 公開 API に PDF 構造タグの取得 API がなく、アウトラインなしの tagged PDF の見出し対応が不足する | 不採用 |
| Qt Quick + 直接 PDFium + Qt WebEngine | PDF 描画、構造、座標の制御を一つのアダプターに集約できる。PDF 処理の障害を GUI から分離できる | ワーカー通信、キャッシュ、表示部品、ビルドと更新管理が必要。構造関連 API の一部は Experimental | 採用設計 |
| Qt Quick + MuPDF + Qt WebEngine | PDFium と異なる描画系として比較できる | AGPL または商用ライセンスと配布方針の整合を検討する必要がある。別途統合と検証が必要 | 採用再検討時の候補 |
公式の機能・ライセンス・API 根拠は [参考資料](10-references.md) の S01-S14 を参照する。PDFium が PDF.js より常に正確、または Qt を使うだけで軽量になるとは判断していない。Qt WebEngine を含めた配布サイズとメモリ量を測定対象にする。
## 3. 設計判断記録
| ID | 決定 | 根拠と結果 | 要件 |
|---|---|---|---|
| ADR-001 | デスクトップ GUI と TUI 風 UI を採用 | 利用者が GUI を選択済み。PDF は本来のレイアウトを保持して描画し、周囲の UI を簡素化する | R01, R05, R08, R15 |
| ADR-002 | PDFium の公開 C API を独立ワーカーに集約 | tagged PDF の構造情報と描画を同じ PDF バックエンドで扱う。Qt PDF の private API は利用しない | R01, R06, R07, R09 |
| ADR-003 | PDF ワーカー内の PDFium 呼び出しを直列化 | PDFium API は thread-safe ではない。UI と独立したキューで実行し、採用版で確認した中断方法とプロセス終了による回復を設計する | R11, R12 |
| ADR-004 | HTML / EPUB に Qt WebEngine を使用 | 現代的な CSS、縦書き、フォント等をブラウザ描画系に委ねる。EPUB のコンテナー、目次、読書順序はアプリ側で扱う | R02, R03, R04, R06, R07 |
| ADR-005 | PDF 描画ルートを一本化 | Qt WebEngine 内蔵 PDF ビューアを無効にし、PDF リンクはアプリの PDF オープン処理へ渡す。Qt PDF と直接 PDFium を併用しない | R01, R05, R09 |
| ADR-006 | 文書内容とアプリ操作の権限を分離 | 文書を未信頼入力として扱い、文書のスクリプトや設定が任意ファイル・外部通信・OS コマンドへ到達しない境界を設ける | R02, R03, R04, R09, R10 |
| ADR-007 | 既存の見出し情報を使う | PDF outline と構造タグ、HTML 見出し、EPUB の目次を区別する。文字サイズからの見出し推定を必須機能にしない | R06, R07 |
| ADR-008 | PDF は閲覧専用とする | 元ファイルを書き換えない。既存の注釈や保存済みフォーム appearance の表示と、編集操作の提供を分ける | R01, R14 |
| ADR-009 | 設定と文書アダプターを分離 | キー入力はコマンドへ変換し、フォーマット固有処理はアダプターへ委譲する。設定ファイルは OS の慣習に沿って配置する | R05, R09, R10, R13 |
| ADR-010 | 初期サポート OS を明示する | Linux 全般という無限定な互換性保証を避け、実機検証済みの組合せをサポート表で公開する | R12 |
Qt WebEngine 自体にも PDFium 由来コードが含まれ得るため、ADR-005 は配布物全体から PDFium の重複がなくなることを意味しない。独自 PDF ワーカーと WebEngine は、それぞれの依存部品と更新責任を記録する。
## 4. 実装前の技術ゲート
G0は以下の技術ゲート一式を実施する段階の総称である。以下は後続実装で採用構成を確定するための条件である。本設計時点で合格を確認したものではない。画質判定と性能の定量基準は品質・試験設計に定義する。
| ゲート | 検証内容 | 合格条件・証跡 | 不合格時の判断 |
|---|---|---|---|
| G-RENDER | 代表 PDF の描画。日本語横組み・縦組み、埋込/非埋込フォント、CID/CMap、透明、クリップ、画像、回転/CropBox、ICC/CMYK、既存注釈、保存済みフォーム appearance | 人が承認した正解画像との比較と差分レビューを完了。文字・図の欠落、誤配置、読めない描画が残っていない | PDFium の設定・版を見直す。要件を満たせなければ別エンジンを比較し ADR を更新 |
| G-HEADINGS | outline なしの H/H1..H6、RoleMap、複数 MCID、Form XObject、回転、ページにまたがる構造 | 見出し列と順序が妥当で、正しいページ・位置へ移動する。座標が得られない場合のページ単位移動を区別して報告できる | 必要な公開 API の不足を特定。黙ってタグ対応を省略せず、アダプター/エンジンの再選定へ戻る |
| G-SANDBOX | Linux と Windows の PDF ワーカー権限制限、クラッシュ回復、IPC 境界 | 許可された入力と通信のみ成功。任意パスの読取・書込、外部通信、子プロセス生成を拒否する試験の記録。GUI が継続しワーカーを再作成できる | 配布対象ごとに制限方式を再設計。sandbox を無効化する起動オプションを通常運用の解決策にしない |
| G-WEB | 文書別 origin、文書スクリプト抑止、アプリ所有の isolated world 操作、読み取り範囲、ページ遷移、リンク、ダウンロード、通信遮断、内蔵 PDF ビューア無効 | HTML / EPUB の表示と見出し操作が機能し、書籍からアプリ権限や許可外リソースへ到達しない。通常 HTML と ZIP / EPUB の双方で確認 | Qt WebEngine 設定・scheme handler・操作経路を修正。文書 JavaScript を広く許可して回避しない |
| G-DISTRIBUTION | クリーン OS 環境での配布と依存・ライセンス確認 | ランタイム、プラグイン、WebEngine 補助プロセス、フォント、PDFium 等の不足なし。配布物と対応ソース・告知・依存一覧の対応が確認済み | パッケージと採用条件を見直して再試験 |
PDF 正解画像は一つのビューアの出力だけで無条件に決めない。仕様に基づく期待、複数の独立した描画系、元文書の作成条件、人による確認を使う。アンチエイリアスの軽微な差と内容の欠落を同じ問題として扱わない。特殊な印刷表現や壊れた文書は、対応範囲・既知制約を明記する。
PDFium の構造タグ API には Experimental な項目がある。必要な API の一覧、固定コミット、文字列長・寿命・エラー処理、MCID と座標の対応を検証記録に残す。見出しのラベルを得ても、正しい位置へ移動できたとは判定しない。
## 5. OS と配布方針
| 環境 | 初期方針 | 必須確認 |
|---|---|---|
| Windows 11 x64 | 正式サポートの提案対象 | クリーン環境、表示倍率、IME、非 ASCII パス、ワーカー権限、署名済み配布物、アンインストール |
| Ubuntu 24.04 x64 | 正式サポートの提案対象。X11 / Wayland を個別に確認 | GPU と software rendering、フォント、IME、ファイル選択、sandbox、Qt / WebEngine の共有ライブラリ依存 |
| Fedora x64 | 追加検証対象 | 対象版を実装時に選定。Wayland、SELinux、ライブラリ、sandbox、パッケージ形式を検証してからサポートを宣言 |
| その他 Linux、Windows on ARM、旧 Windows、macOS | 初期の正式サポートに含めない | 要望と検証コストに基づき別途追加 |
Qt の一般的なサポート対象と、本アプリケーションで検証した対象は区別する。Qt WebEngine には独自のビルド制約があり、一般的な Qt 構成のすべてを利用できるわけではない。確認した資料では Windows の MinGW ビルドは Qt WebEngine に適合せず、静的ビルドも非対応である。[Qt WebEngine Platform Notes](https://doc.qt.io/qt-6/qtwebengine-platform-notes.html)
Windows は MSVC 系の Qt と互換性のあるツールチェーンを使い、PDFium のビルドは upstream の Clang 系要件と整合させる。Linux では対象環境に合う共有ライブラリと sandbox を梱包・依存解決する。具体的なパッケージ形式は G-DISTRIBUTION で比較し、初期対象ごとに一つを決める。単一実行ファイル化や任意 Linux での起動を前提にしない。
ライセンスはライブラリ名だけで結論を出さず、実際に同梱する版・モジュール・ビルド方式で確認する。Qt 商用ライセンスの採用だけで Chromium 等の第三者条件が消えるとは扱わない。MuPDF は代替評価対象にとどめ、ライセンス整理を行わず自動フォールバックとして同梱しない。
## 6. 後続の実装順序
| 段階 | 完了させるもの | 次へ進む条件 |
|---|---|---|
| 0. 設計基準の固定 | 対応範囲、テスト文書、参照画像、初期 OS、設定形式、依存版、ライセンス方針 | 未決事項の担当と期限が決まり、上記ゲートの評価環境が用意されている |
| 1. PDF 基盤の検証 | 描画、構造見出し、座標、ワーカー制御、権限制限、最小配布経路 | G-RENDER / G-HEADINGS / G-SANDBOX を満たし、配布成立の見通しがある |
| 2. PDF 閲覧機能 | 通常表示、移動、拡大縮小、目次、見出し、ページ一覧、設定、状態復元 | PDF の機能・性能・操作性の受入試験に合格 |
| 3. HTML / ZIP HTML / EPUB | パッケージ読込、リソース解決、目次、見出し、読書順序、位置復元 | G-WEB と各形式の受入試験に合格 |
| 4. 製品化 | OS 別配布、クリーンインストール、更新、障害回復、ヘルプ、告知文書 | 全対象 OS の回帰試験と G-DISTRIBUTION に合格 |
PDF 最優先は開発・検証順序を意味する。最終的な初期リリースが要件を満たすためには、HTML、ZIP HTML、EPUB を含む全必須要件を受け入れる必要がある。PDF のみの中間成果物を要件一式の完成とは扱わない。
## 7. 継続管理する不確実性
| 項目 | 設計時点の扱い | 解消時点 |
|---|---|---|
| 実際の書籍での PDF 描画互換性 | 代表コーパスは後続で収集・承認。完全一致は未保証 | G-RENDER |
| tagged PDF の全構造と座標 | 公開 API による経路を採用し、特殊構造は要検証 | G-HEADINGS |
| PDFium の固定版と更新頻度 | 実装開始時にコミットを固定し、脆弱性・互換性を継続評価 | 段階 0 以降 |
| ワーカーの OS 別権限制限 | 別プロセス化と sandbox を別の要件として扱う | G-SANDBOX |
| WebEngine の文書操作と無権限化の両立 | 文書の JavaScript とアプリ所有の操作を区別して検証 | G-WEB |
| 配布ライセンスとパッケージ方式 | 実際の依存集合に基づいて固定 | G-DISTRIBUTION |
| 性能の成立 | 品質・試験設計の測定条件と合格値を使用。未測定 | 各段階の受入試験 |
+64
View File
@@ -0,0 +1,64 @@
# 10 参考資料
確認日: 2026-09-19
一次資料を用いて API、機能、ライセンス表記、サポート条件を確認した。Web 上の最新資料は変更されるため、実装開始時には採用版に対応した資料・ソースコミットも固定する。以下の資料は設計の根拠であり、本アプリケーションの描画品質、性能、ライセンス適合性を実証するものではない。
## 1. PDF と技術選定
| ID | 資料 | 本設計で確認した事項 |
|---|---|---|
| S01 | [PDFium README](https://pdfium.googlesource.com/pdfium/+/refs/heads/main/README.md) | Linux / Windows のビルド経路、JavaScript / XFA のビルド選択、公開 API の範囲、ピクセル試験の存在 |
| S02 | [PDFium fpdfview.h](https://pdfium.googlesource.com/pdfium/+/main/public/fpdfview.h) | 公開 C API、読込と描画、API が thread-safe ではないこと |
| S03 | [PDFium fpdf_structtree.h](https://pdfium.googlesource.com/pdfium/+/main/public/fpdf_structtree.h) | ページの構造ツリー、要素型、子要素、MCID の取得。一部 API が Experimental であること |
| S04 | [PDFium fpdf_edit.h](https://pdfium.googlesource.com/pdfium/+/main/public/fpdf_edit.h) | ページオブジェクトの読取、MCID と境界情報へのアクセス。ファイル名に edit を含むが、読取 API の参照に使う |
| S05 | [PDFium LICENSE](https://pdfium.googlesource.com/pdfium/+/refs/heads/main/LICENSE) | PDFium のライセンス本文。依存部品の条件を含めて確認する起点 |
| S06 | [Qt PDF](https://doc.qt.io/qt-6/qtpdf-index.html) | Qt PDF の描画、ビューア、目次、リンク、検索等の機能 |
| S07 | [QPdfDocument](https://doc.qt.io/qt-6/qpdfdocument.html) | 読込、描画、ページラベル、テキスト、エラーの公開 API。構造タグ API の有無を評価する対象 |
| S08 | [Qt PDF Licensing](https://doc.qt.io/qt-6/qtpdf-licensing.html) | Qt PDF の商用 / LGPLv3 / GPLv2 表記と、PDFium スナップショット・第三者ライセンス |
| S09 | [PDF.js 公式サイト](https://mozilla.github.io/pdf.js/) | PDF.js の位置づけと Apache 2.0 の表記 |
| S10 | [PDF.js FAQ](https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions) | ブラウザ環境による対応差、Worker と本体のバージョン整合、可視ページを優先するメモリ上の考え方 |
| S11 | [Electron Security](https://www.electronjs.org/docs/latest/tutorial/security) | Node.js 能力の分離、context isolation、sandbox、IPC 検証、依存更新の必要性 |
| S12 | [Electron Releases](https://www.electronjs.org/docs/latest/tutorial/electron-timelines) | 直近三つの stable major release というサポート方針と更新周期 |
| S13 | [What is MuPDF?](https://mupdf.readthedocs.io/en/latest/guide/what-is-mupdf.html) | PDF 等の閲覧・変換・操作のためのライブラリとツールという位置づけ |
| S14 | [MuPDF License](https://mupdf.readthedocs.io/en/latest/license.html) | AGPL または商用ライセンスという提供形態 |
## 2. Qt WebEngine と配布
| ID | 資料 | 本設計で確認した事項 |
|---|---|---|
| S15 | [QWebEngineSettings](https://doc.qt.io/qt-6/qwebenginesettings.html) | MainWorld の JavaScript、ローカルファイル/外部 URL、DNS prefetch、内蔵 PDF ビューア等の設定 |
| S16 | [Qt WebEngine Platform Notes](https://doc.qt.io/qt-6/qtwebengine-platform-notes.html) | Chromium renderer の sandbox、Linux の実行条件、C++20、Windows ビルド制約、静的ビルド非対応 |
| S17 | [Qt WebEngine Licensing](https://doc.qt.io/qt-6/qtwebengine-licensing.html) | Qt 部分と Chromium 側の両方の条件が関係すること、第三者ライセンスの一覧 |
| S18 | [Deploying Qt WebEngine Applications](https://doc.qt.io/qt-6/qtwebengine-deploying.html) | 補助プロセス、リソース、翻訳等を含む配布の検討事項 |
| S19 | [Qt Supported Platforms](https://doc.qt.io/qt-6/supported-platforms.html) | Windows 11 / Ubuntu 24.04 の掲載、版ごとのサポート条件、モジュール固有の例外 |
## 3. 文書形式・隔離・保存
| ID | 資料 | 本設計で確認した事項 |
|---|---|---|
| S20 | [PDFium fpdf_formfill.h](https://pdfium.googlesource.com/pdfium/+/main/public/fpdf_formfill.h) | フォーム環境とwidget描画。通常ページ描画との違い |
| S21 | [EPUB 3.3](https://www.w3.org/TR/epub-33/) | package、spine、nav、rendition、font難読化 |
| S22 | [EPUB Reading Systems 3.3](https://www.w3.org/TR/epub-rs-33/) | rootfile、XML処理、スクリプトfallback、ローカル参照の制約 |
| S23 | [EPUB CFI 1.1](https://idpf.org/epub/linking/cfi/epub-cfi.html) | 文書の論理位置表現 |
| S24 | [QWebEngineScript](https://doc.qt.io/qt-6/qwebenginescript.html) | ApplicationWorld、DOMアクセスと変数の分離 |
| S25 | [WebEngineView](https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html) | runJavaScriptのworld、ナビゲーション・権限の通知 |
| S26 | [QWebEngineUrlScheme](https://doc.qt.io/qt-6/qwebengineurlscheme.html) | Host構文によるoriginとscheme権限 |
| S27 | [QWebEngineUrlSchemeHandler](https://doc.qt.io/qt-6/qwebengineurlschemehandler.html) | 文書資源の応答 |
| S28 | [QWebEngineUrlRequestJob](https://doc.qt.io/qt-6/qwebengineurlrequestjob.html) | Qt 6.6以降の追加応答ヘッダー |
| S29 | [QWebEngineUrlRequestInterceptor](https://doc.qt.io/qt-6/qwebengineurlrequestinterceptor.html) | 要求の事前検査 |
| S30 | [QQuickWebEngineProfile](https://doc.qt.io/qt-6/qquickwebengineprofile.html) | 文書ごとのprofileとhandler |
| S31 | [Linux Landlock](https://docs.kernel.org/userspace-api/landlock.html) | OSアクセス制限とABIによる機能差 |
| S32 | [Windows AppContainer](https://learn.microsoft.com/en-us/windows/win32/secauthz/implementing-an-appcontainer) | 制限されたプロセス環境の構成 |
| S33 | [OWASP Archive Testing](https://wstg.owasp.org/latest/4-Web_Application_Security_Testing/10-Business_Logic/09-Upload_of_Malicious_Files/) | 不正アーカイブの試験観点 |
| S34 | [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html) | 解凍後サイズの制限 |
| S35 | [XDG Base Directory](https://specifications.freedesktop.org/basedir/latest/) | 設定・状態・キャッシュの配置と既定値 |
| S36 | [QStandardPaths](https://doc.qt.io/qt-6/qstandardpaths.html) | OS別保存経路、AppConfigLocationとAppDataLocationの差 |
| S37 | [Windows Known Folders](https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid) | RoamingAppDataとLocalAppData |
## 4. 参照の読み方
- **資料の事実:** 公開 API の存在、スレッド安全性の注意、ライセンス表記、サポート対象等。上表に対応する一次資料で確認する。
- **設計判断:** Qt Quick と直接 PDFium の組合せ、ワーカー分離、初期 OS、PDF 描画ルートの一本化等。[技術選定・設計判断](09-decisions-roadmap.md) の ADR に記録する。
- **未検証事項:** 対象書籍の描画互換性、特殊な構造見出しの座標、実機性能、OS 別 sandbox、最終配布物。公式資料に機能があることだけで合格とせず、受入試験で確認する。
+13
View File
@@ -0,0 +1,13 @@
{
"00-guide.md": "179fa4a28b5ba994d3d71ffca310da78f071520f9fc8b18f529e5e5f2210ac38",
"01-requirements.md": "ee35438cf3c749b7410db2b1e4b941dd3a5fc1e3b4bb1cd6c0e3cf6056c34961",
"02-architecture.md": "afbc10d91dc6ddcd9727f3af1b861535c6d9e6da977ac19a9e2ad55c49bf83c3",
"03-formats.md": "5590019c96f8935175670b9d1a4876d5f365303e63c53953de006cde105eaa53",
"04-ui-navigation.md": "9ccb90ad0549c8a8dc41b2d883842ebcf28358796398cf62c36e218980387456",
"05-data-contracts.md": "2b9d28523e68950b48d57db363c663416c957145ad3f4e460774ac3740287a71",
"06-config-storage.md": "c7e00e61f686c36288a87f01dc00429a68174001984def78b75446b1e6c954ed",
"07-security-errors.md": "855450a9791dce0e6afb9f0e18f2f5bbd7b6cecfe6cf8c5ad677ac2af5608c8a",
"08-performance-tests.md": "eb852e6b6016e732c715ed061ea25ed1a911c092bb6de6e71aabbf4acb4f6483",
"09-decisions-roadmap.md": "55c176637d017acc66b46ba1780100854f671442d465c329b5d258f4c8fb2ebb",
"10-references.md": "4d0614abd7104a1e68c3b061fc797c3e1576d1127907a08c75c6e008da2ca0da"
}