From 3476beabc378cac9df2f532d173f283a961af6ab Mon Sep 17 00:00:00 2001 From: zhanghongyuan Date: Mon, 14 Sep 2026 10:31:39 +0800 Subject: [PATCH 1/2] fix(reader): clamp OFD render slices to page canvas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The big-image task split pages into 1000px-wide tiles without clamping the last tile to the actual page width. Pages whose width was not a multiple of 1000 produced out-of-canvas requests that the OFD document model rejects, leaving the right edge unpainted. The magnifier (getImagePoint) could also request rects beyond the canvas near page edges; intersect the rect with the canvas first. 大图任务按1000px分片时末尾未截断到页面实际宽度,页宽非1000整数倍 时越界请求被OFD文档模型拒绝,页面右侧无法绘制;放大镜在页边悬停 时同样可能越界,先与整页画布求交集。 Log: 修复OFD分片越界导致页面右侧渲染缺失 Influence: OFD文档大图分片与放大镜渲染不再产生越界请求,页面右侧不再缺失。 --- reader/browser/BrowserPage.cpp | 12 ++++++++++-- reader/browser/PageRenderThread.cpp | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/reader/browser/BrowserPage.cpp b/reader/browser/BrowserPage.cpp index 2df1063f7..05122aadb 100644 --- a/reader/browser/BrowserPage.cpp +++ b/reader/browser/BrowserPage.cpp @@ -564,8 +564,16 @@ QImage BrowserPage::getImagePoint(double scaleFactor, QPoint point) int ss = static_cast(122 * scaleFactor / m_scaleFactor); - QRect rect = QRect(qRound(point.x() * scaleFactor / m_scaleFactor - ss / 2.0), - qRound(point.y() * scaleFactor / m_scaleFactor - ss / 2.0), ss, ss); + // 悬停点靠近页面边缘时,请求矩形可能超出整页画布; + // 文档模型拒绝越界渲染请求,需先与画布求交集(与 getImage 的取整方式保持一致) + const QRect canvas(0, 0, static_cast(m_originSizeF.width() * scaleFactor), + static_cast(m_originSizeF.height() * scaleFactor)); + const QRect rect = QRect(qRound(point.x() * scaleFactor / m_scaleFactor - ss / 2.0), + qRound(point.y() * scaleFactor / m_scaleFactor - ss / 2.0), ss, ss) + .intersected(canvas); + + if (rect.isEmpty()) + return QImage(); return m_sheet->renderer()->getImage(itemIndex(), static_cast(m_originSizeF.width() * scaleFactor), diff --git a/reader/browser/PageRenderThread.cpp b/reader/browser/PageRenderThread.cpp index 3bcb3c7bb..6dcdfb043 100644 --- a/reader/browser/PageRenderThread.cpp +++ b/reader/browser/PageRenderThread.cpp @@ -362,9 +362,10 @@ void PageRenderThread::run() //if has signature,render whole rect renderRects.append(task.rect); } else { - int wCount = task.rect.width() % 1000 == 0 ? (task.rect.width() / 1000) : (task.rect.width() / 1000 + 1); - for (int i = 0; i < wCount; ++i) {//只能以宽度前进(即只能分割宽度),如果x从0开始,每次都将消耗一定时间 - renderRects.append(QRect(i * 1000, 0, 1000, task.rect.height())); + // 末尾分片须截断到页面实际宽度,否则越界请求会被文档模型拒绝, + // 页面右侧会因取到空图而残留未绘制区域 + for (int i = 0; i < task.rect.width(); i += 1000) {//只能以宽度前进(即只能分割宽度),如果x从0开始,每次都将消耗一定时间 + renderRects.append(QRect(i, 0, qMin(1000, task.rect.width() - i), task.rect.height())); }; } From ce3b75df7184b9cae40158ffce15151c2fd3610f Mon Sep 17 00:00:00 2001 From: zhanghongyuan Date: Mon, 14 Sep 2026 13:38:01 +0800 Subject: [PATCH 2/2] chore(docs): remove non-standard docs/superpowers directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/superpowers 目录为开发过程中的临时设计记录与计划文档, 不符合项目正式文档规范,按维护要求整体移除。相关设计说明由 代码注释与 rofd 仓库文档承载。 Log: 移除不规范的docs/superpowers目录 Influence: 仅删除开发期文档,不影响编译、功能与打包。 --- .../plans/2026-09-10-ofd-semantic-text.md | 106 --------- .../plans/2026-09-11-ofd-navigation.md | 201 ------------------ .../2026-09-10-ofd-semantic-text-design.md | 62 ------ .../specs/2026-09-11-ofd-navigation-design.md | 149 ------------- 4 files changed, 518 deletions(-) delete mode 100644 docs/superpowers/plans/2026-09-10-ofd-semantic-text.md delete mode 100644 docs/superpowers/plans/2026-09-11-ofd-navigation.md delete mode 100644 docs/superpowers/specs/2026-09-10-ofd-semantic-text-design.md delete mode 100644 docs/superpowers/specs/2026-09-11-ofd-navigation-design.md diff --git a/docs/superpowers/plans/2026-09-10-ofd-semantic-text.md b/docs/superpowers/plans/2026-09-10-ofd-semantic-text.md deleted file mode 100644 index 9ceaf16d4..000000000 --- a/docs/superpowers/plans/2026-09-10-ofd-semantic-text.md +++ /dev/null @@ -1,106 +0,0 @@ -# OFD Semantic Text Integration Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Make OFD documents participate in the reader's existing text extraction, search, selection, and copy workflows using the current `rofd` semantic C ABI. - -**Architecture:** Keep semantic adaptation inside `OfdPage`. Convert between physical-page millimetres and reader logical pixels at the boundary, use owned result handles per query, and expose character layout through the existing `Word` list. - -**Tech Stack:** C++17, Qt Core/Gui, GoogleTest, `rofd` stable C ABI - ---- - -### Task 1: Fixture-backed semantic behavior - -**Files:** -- Modify: `tests/document/ut_ofdmodel.cpp` - -- [ ] **Step 1: Add failing tests** - -Add tests that load `normal.ofd`, require non-empty full-page text, extract text -from the first returned word rectangle, verify case-sensitive and whole-word -search filtering, and require non-empty `words()` entries with valid geometry. - -- [ ] **Step 2: Run the focused tests and verify RED** - -Run: - -```bash -QT_QPA_PLATFORM=offscreen build-semantic/tests/test-deepin-reader \ - --gtest_filter='TestOfdModel.semantic*' -``` - -Expected: the text, search, and words assertions fail because `OfdPage` still -returns empty values. - -### Task 2: Coordinate-safe semantic adapter - -**Files:** -- Modify: `reader/document/OfdModel.h` -- Modify: `reader/document/OfdModel.cpp` -- Test: `tests/document/ut_ofdmodel.cpp` - -- [ ] **Step 1: Store physical page geometry and declare `words()`** - -Add `QList words() override`, store the page's complete `rofd_rect_t`, and -declare private `toMillimetres` and `toPixels` rectangle helpers. - -- [ ] **Step 2: Implement owned-string extraction** - -Use `rofd_page_get_text` for a null rectangle and -`rofd_page_get_text_for_area` otherwise. Convert the borrowed UTF-8 bytes with -an explicit length before freeing `rofd_string_t`. - -- [ ] **Step 3: Implement semantic search** - -Initialize `rofd_find_options_t`, set `ROFD_FIND_CASE_SENSITIVE` and -`ROFD_FIND_WHOLE_WORDS` as requested, enumerate matches, convert each match -rectangle, and append `PageSection{PageLine{QString(), rect}}`. - -- [ ] **Step 4: Implement selectable character layout** - -Acquire canonical text and a layout snapshot, skip synthesized separators and -zero-area geometry, validate every UTF-8 span, and append one `Word` per scalar -using the converted rectangle. - -- [ ] **Step 5: Run focused tests and verify GREEN** - -Run the semantic test filter and expect every new test to pass. - -### Task 3: Build contract and regression verification - -**Files:** -- Modify only if required by configure checks: `CMakeLists.txt` -- Modify: `debian/control` only after the semantic package version is known - -- [ ] **Step 1: Configure against local current `rofd main`** - -Build `rofd-ffi` into an isolated `/tmp` target, then configure a fresh reader -build using `-DROFD_ROOT=/home/hualet/projects/hualet/rofd` and the matching -library directory. - -- [ ] **Step 2: Build the reader and tests** - -Run the normal CMake build for `test-deepin-reader` and `deepin-reader`. - -- [ ] **Step 3: Run all OFD model tests** - -Run: - -```bash -QT_QPA_PLATFORM=offscreen build-semantic/tests/test-deepin-reader \ - --gtest_filter='TestOfdModel.*' -``` - -Expected: all OFD model tests pass. - -- [ ] **Step 4: Run the complete document-model test subset** - -Run the repository's document model test filters and report any unrelated -pre-existing failures separately. - -- [ ] **Step 5: Inspect the final diff** - -Confirm only the OFD adapter, OFD tests, and approved build-contract changes are -present. Leave implementation changes uncommitted until the user requests a -commit. diff --git a/docs/superpowers/plans/2026-09-11-ofd-navigation.md b/docs/superpowers/plans/2026-09-11-ofd-navigation.md deleted file mode 100644 index 09770f1b0..000000000 --- a/docs/superpowers/plans/2026-09-11-ofd-navigation.md +++ /dev/null @@ -1,201 +0,0 @@ -# OFD Navigation Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking. - -**Goal:** Expose OFD outlines and clickable page links without losing destination semantics or enabling automatic actions. - -**Architecture:** Add optional typed navigation values to the existing model. A pure view calculator and URI validator are shared by outline and page-link activation; rofd remains behind the OFD adapter. Preserve legacy PDF/XPS navigation when the optional value is absent. - -**Tech Stack:** C++17, Qt Widgets, rofd 0.4.0 C ABI, GoogleTest, CMake. - -## Execution and build boundaries - -Use the approved design at `docs/superpowers/specs/2026-09-11-ofd-navigation-design.md`. -The isolated worktree is `/tmp/deepin-reader-ofd-nav.bx98sU/tree`; the baseline is -`e50708ff`. The paired rofd header/library are in the sibling `rofd/` directory. -Upstream advanced to `35cb164` / 0.4.0 after design approval, so update the package -minimum instead of retaining the design's historical 0.3.0 limitation. - -Run only focused tests and the reader target, with `-j1`. Never build the -`test-deepin-reader` aggregate. The clean baseline passes all 19 OFD tests. - -```sh -cmake -S tests/ofd-model -B build/ofd-model \ - -DROFD_INCLUDE_DIR=/tmp/deepin-reader-ofd-nav.bx98sU/rofd/include \ - -DROFD_FFI_LIBRARY=/tmp/deepin-reader-ofd-nav.bx98sU/rofd/librofd_ffi.so -cmake --build build/ofd-model -j1 -ctest --test-dir build/ofd-model --output-on-failure -``` - -### Task 1: Typed targets and pure navigation calculations - -**Files:** Create `reader/document/Navigation.h`, `Navigation.cpp`, -`tests/document/ut_navigation.cpp`; modify `reader/document/Model.h` and -`tests/ofd-model/CMakeLists.txt`. - -- [x] Add tests first for missing/zero fields, all five modes, invalid rectangles, - scale limits, 90/180/270-degree rotation, double-page viewport allocation, - allowed URI resolution, rejected file/relative/unknown URI, and legacy defaults. -- [x] Run the focused target and capture the missing-behavior failure before implementation. -- [x] Implement this shared contract (namespace `deepin_reader`): - -```cpp -enum class DestinationMode { XYZ, Fit, FitH, FitV, FitR }; -struct NavigationDestination { - int pageIndex = -1; - DestinationMode mode = DestinationMode::XYZ; - std::optional left, top, right, bottom, zoom; - bool isValid() const; -}; -struct NavigationTarget { - std::optional destination; - QUrl uri; - bool isValid() const; -}; -struct NavigationView { - qreal scale = 1; - QRectF focusRect; // unscaled page coordinates; zero-size rectangle for a point -}; -QUrl resolveNavigationUri(const QString &uri, const QString &base = QString()); -std::optional navigationView( - const NavigationDestination &destination, const QSizeF &pageSize, - const QSizeF &viewportSize, const QPointF ¤tPosition, - qreal currentScale, qreal maximumScale, int rotationDegrees, bool twoPages); -``` - - `Section` and `Link` gain `std::optional navigation`; - `Section` additionally gains `std::optional expanded`. Register the - target metatype. Absent navigation retains old `Link::isValid()` behavior; - present navigation is checked on its own, never falling back to stale fields. - Calculate fit scale using rotated bounds and half viewport width for two pages, - clamp to `[0.1, maximumScale]`, reject non-finite inputs and invalid FitR. - XYZ defaults use currentPosition; zoom absent/zero preserves currentScale. - URI resolution accepts only absolute HTTP/HTTPS with host and nonempty mailto, - uses QUrl strict parsing and explicit Base, and never performs I/O. -- [x] Verify all tests, including `Link legacy; legacy.page = 1; EXPECT_TRUE(legacy.isValid());`. -- [x] Review the exact diff for spec compliance, then code quality, before consuming the contract. - -### Task 2: OFD outlines and destinations - -**Files:** Modify `reader/document/OfdModel.h`, `OfdModel.cpp`, -`tests/document/ut_ofdmodel.cpp`, `CMakeLists.txt`, `debian/control`. - -- [x] Add controlled two-page OFD fixtures with page origins `(7,11)` and `(3,5)`, - nested titles four levels deep, a non-clickable parent, named and explicit - destinations, zero/omitted fields, invalid targets and non-CLICK actions. - First assertion against the current stub is `ASSERT_EQ(doc->outline().size(), 1);`. -- [x] Run and observe the empty-outline failure. -- [x] Implement `Outline outline() const override` with a mutex-protected cache, - including empty/failed results. Convert preorder nodes in reverse index order - into the tree after validating parent-before-child indices; retain invalid-target - nodes without a page number. Copy all strings before releasing the RAII snapshot. - A document helper maps rofd actions/destinations to Task 1's values and lazily - caches target-page physical rectangles. Only CLICK Goto or allowed URI actions - become targets; preserve optional coordinates and use target-page origins. -- [x] Probe outline/destination functions at configure time and require - `librofd-ffi-dev (>= 0.4.0)`; refresh warnings after lazy navigation queries. -- [x] Verify geometry with `EXPECT_NEAR(*target.destination->left, (13-3)*dpi/25.4, 1e-6);`, - unresolved page targets remain absent, and repeated outline calls are stable. -- [x] Run spec review then quality review; checkpoint the scoped backend change. - -### Task 3: Complete catalog and view execution - -**Files:** Create `reader/sidebar/CatalogOutlineModel.h/.cpp` and -`tests/document/ut_catalogoutlinemodel.cpp`; modify `CatalogTreeView.h/.cpp`, -`reader/uiframe/DocSheet.h/.cpp`, `reader/browser/SheetBrowser.h/.cpp`, -`tests/ofd-model/CMakeLists.txt`. - -- [x] Test model construction first: a targetless root with a four-level child - must remain present, have a blank page column, retain navigation/expansion roles, - and expose the leaf. Preserve legacy page and offset roles. -- [x] Observe a failing model test, then implement reusable iterative tree-to-item - population. Bind both columns to the same target. CatalogTreeView uses it, - expands OFD defaults, and does not activate targets while populating or syncing. -- [x] Add `bool navigateTo(const deepin_reader::NavigationTarget &)` to SheetBrowser - and a forwarding DocSheet method. Validate page count before changing state. - Compute currentPosition by mapping viewport origin to the current page and - dividing by current scale. Apply Task 1's view, call setScaleFactor, and map - focusRect at the actual resulting scale through the target item's scene transform - before setting scrollbars and notifying the current page. -- [x] For a typed URI, revalidate, display the final URL in SecurityDialog, and - call QDesktopServices only after Accepted. Share this path with Task 4. -- [x] Enable PREVIEW_CATALOG for OFD. Typed outline actions execute on explicit - click or keyboard activation exactly once, not currentChanged; legacy paths - remain unchanged. Stored expansion state, including all-collapsed state, wins - over defaults when the catalog is opened lazily. -- [ ] Verify model/calculator tests and reader compilation; exercise actual - outline activation, rotation and cancellation in a controlled app window. -- [x] Spec review then quality review; checkpoint directory/navigation integration. - -### Task 4: Page link adapter and shared activation - -**Files:** Modify `reader/document/OfdModel.h/.cpp`, -`tests/document/ut_ofdmodel.cpp`, `reader/browser/SheetBrowser.cpp`, `CMakeLists.txt`. - -- [x] Add links to controlled fixtures with separated regions, overlaps, - transformed bounds, CLICK/PO/DO events, URI/Base and GotoA actions. - First assertion against the stub is `EXPECT_TRUE(page->getLinkAtPoint(hit).isValid());`. -- [x] Run and observe that missing-link failure. -- [x] Implement a per-page immutable Qt-owned link cache guarded during init. - Preserve source order, use the first supported hit, and union independent - rectangles as paths without filling the gaps. Copy borrowed data before freeing - the snapshot. Empty/failed lists are cached. Reuse Task 2 conversion and refresh - warnings after rofd_page_get_links. No adapter action executes external code. -- [x] `SheetBrowser::jump2Link` calls navigateTo when `link.navigation` exists; - otherwise preserves the old PDF/XPS path. Keep resolved URI text for hover tips. -- [x] Extend CMake symbol checks and tests for gaps, deterministic overlap, - unsafe targets, failure isolation from outlines and repeated queries. -- [x] Run spec review then quality review; checkpoint page-link integration. - -### Task 5: Integrated validation and delivery - -**Files:** Update `tests/ofd-model/README.md` and this checklist with actual results. - -- [x] Run `git diff --check`, all focused tests, and inspect linked rofd SONAME. -- [x] Build only `deepin-reader -j1`, reusing the existing PDFium build where possible. -- [ ] Verify real window catalog depth/activation, page link hover/click, rotation, - scale changes and external-link cancellation. Do not visit test URLs. -- [x] Check legacy PDF/XPS targets keep their prior path and no new automatic actions - occur on opening a document, restoring state or selecting a catalog item. -- [x] Review the entire diff after task reviews, report exact evidence and any - unverified conditions. Keep outline/navigation and page links separable in history; - do not push or merge to main without the user's request. - -## Execution evidence (2026-09-11) - -- Paired rofd snapshot: `35cb164`, version 0.4.0; the source repository was not - modified. The original reader worktree remains on `ofd_support`. -- Task 1: 22 navigation/legacy tests passed after the missing-behavior RED run. - Task 2: five new outline tests first failed on the empty inherited outline. - Task 3: six catalog-model tests passed after their RED runs. -- Task 4: six link tests first failed on the inherited empty link. The additional - missing-owner test reproduced a crash before the guard was added. The GUI - regression also failed with the backend connected but the old browser route: - a real mouse click changed pages but lost the destination's Fit scale. -- Fresh focused test run: **59 tests passed**, seven suites, no skips. -- Fresh `deepin-reader -j1` build succeeded with `BUILD_TESTS=OFF` and the existing - PDFium shared library. No aggregate test build or OOM occurred. -- Reproducible real-widget checks: **247 assertions passed** through - `python3 tests/ofd-model/run_navigation_smoke.py build/reader-local`. - Includes deep catalogs, inert selection, explicit Enter/click activation, - all five modes at four rotations and two layouts, omitted coordinates, - page-link hover and actual mouse press/release, Fit semantics, and URI cancel. - External test URLs were intercepted and never opened. -- `ldd` resolves `librofd_ffi.so.0` to the paired sibling `rofd/` snapshot and - PDFium to the original reader's existing shared build. A negative configure - check with the old rofd library correctly failed on missing navigation APIs. -- `git diff --check` passes. Spec and quality reviews passed for all four - implementation tasks and the integrated change, with no critical or important - findings. Direct fitting-ratio overflow/underflow coverage remains an optional - test enhancement; the bounded implementation was reviewed. -- Scoped implementation commits: `f5be344a` (typed targets/calculations), - `db340c99` (outline adapter), `d4336b10` (catalog/navigation UI and widget runner), - `b2431562` (page links and shared activation). - -### Validation limit - -The real-widget checks use Qt's **offscreen** platform. Visible desktop-window -acceptance remains unchecked above: the native UI automation runtime was not -available. The aggregate test executable was deliberately not built, following -the user's reader-only build constraint. No main-branch merge or remote push -has been performed. diff --git a/docs/superpowers/specs/2026-09-10-ofd-semantic-text-design.md b/docs/superpowers/specs/2026-09-10-ofd-semantic-text-design.md deleted file mode 100644 index 240cbec98..000000000 --- a/docs/superpowers/specs/2026-09-10-ofd-semantic-text-design.md +++ /dev/null @@ -1,62 +0,0 @@ -# OFD Semantic Text Integration Design - -## Scope - -Connect the semantic text API already available in the current `rofd` C ABI to -the existing `deepin_reader::Page` abstraction. The change implements OFD page -text extraction, text search, and selectable character geometry. It does not -add OFD-specific UI or attempt metadata, outline, links, annotations, native -writing, or tiled rendering, because those still require new `rofd` APIs. - -## Architecture - -`OfdPage` remains the only adapter between `deepin-reader` page semantics and -`rofd`. It stores the physical page rectangle returned by -`rofd_page_get_size_mm`, then uses small private conversion helpers for physical -millimetres and the logical page-pixel coordinate system used by the reader. - -Each public query creates and frees its own immutable `rofd` result handle. No -mutable semantic cache is shared between render and search worker threads. -This follows the existing adapter structure and the C ABI's concurrent -read-only handle contract. - -## Data Flow - -- `text(rect)` converts the requested logical-pixel rectangle to physical-page - millimetres and calls `rofd_page_get_text_for_area`. A null rectangle requests - the full canonical page text through `rofd_page_get_text`. -- `search(query, matchCase, wholeWords)` initializes - `rofd_find_options_t`, maps the two reader flags to `ROFD_FIND_*`, and turns - every match rectangle into one `PageSection` containing one `PageLine`. -- `words()` gets canonical UTF-8 text plus its layout snapshot. Every - non-synthesized scalar with valid geometry becomes one `Word`; its text is - decoded from the canonical string using the layout's byte offset and length. - One scalar per `Word` matches the existing selection overlay contract. - -## Coordinates - -The reader expresses page text geometry in logical pixels at the document's X -and Y logical DPI. `rofd` expresses geometry in physical-page millimetres. -Conversions include the physical page rectangle's X/Y origin: - -``` -mm.x = physical.x + px.x * 25.4 / xDpi -px.x = (mm.x - physical.x) * xDpi / 25.4 -``` - -The same formula applies independently to Y, width, and height. - -## Error Handling - -Invalid handles, empty search input, invalid rectangles, invalid UTF-8 spans, -and failed C ABI queries return the empty result expected by `Page`. Owned -`rofd` handles are always freed. ABI failures are logged with page index, -status, and the optional error message. - -## Verification - -Fixture-backed tests prove full and area text extraction, case-sensitive and -whole-word search behavior, search rectangles, and selectable character text -and geometry. The tests are first run against the current stubs to demonstrate -the intended failures, then rerun against the implementation using a local -build of the latest `rofd main` semantic ABI. diff --git a/docs/superpowers/specs/2026-09-11-ofd-navigation-design.md b/docs/superpowers/specs/2026-09-11-ofd-navigation-design.md deleted file mode 100644 index 722199d94..000000000 --- a/docs/superpowers/specs/2026-09-11-ofd-navigation-design.md +++ /dev/null @@ -1,149 +0,0 @@ -# OFD 目录与页面链接接入设计 - -## 范围与基线 - -在现有 OFD 渲染、语义文本、元数据和警告支持上,接入 rofd 的目录树、 -跳转目标和页面链接,复用 reader 的侧栏、页面交互及安全确认界面。 - -本次核对的 rofd main 为 `2331199`,包含: - -- `56da925`:像素区域渲染,reader 已接入。 -- `6db9bac`:元数据和警告,reader 已接入。 -- `9e4a169`:目录、动作与目标,本次接入。 -- `2331199`:页面链接映射,本次接入。 - -实施时补充:rofd 随后推进到 `35cb164`,发布版本为 0.4.0。因此本次实现使用 -配套的 0.4.0 头文件和共享库,并将 Debian 最低依赖更新为 0.4.0。 - -不增加附件提取、附件执行、自动动作、脚本、注释编辑或文件写回。 -不重构 PDF/XPS 后端,也不把 rofd 的矩形链接范围描述为精确路径命中。 - -## 架构与兼容性 - -为公共模型增加可选的导航目标,由目录项和页面链接共同使用。目标区分 -页内跳转与外部 URI;页内目标保存零基页码、目标模式、可选坐标和缩放。 -可选值必须区分缺省与显式零,不能只靠浮点数是否为零判断字段存在。 - -`Section`、`Link` 保留原有字段和默认行为。PDF/XPS 等未提供新目标的 -对象继续走原路径;OFD 使用新路径,不复用目录跳转中 PDF 风格的 Y 轴翻转。 -目录项另保存可选的初始展开状态,避免改变其它格式的默认展开行为。 - -职责分配: - -- `OfdDocument`:获取目录快照、转换树和导航目标,缓存目标页尺寸。 -- `OfdPage`:获取并缓存页面链接,完成命中查询,不执行任何动作。 -- 公共导航转换/计算辅助代码:校验目标、转换坐标、计算缩放与滚动位置; - 将可独立验证的计算从大型浏览器类中分离。 -- `CatalogTreeView`:完整展示树,将目录用户操作交给导航入口。 -- `DocSheet` / `SheetBrowser`:执行目标,更新缩放、布局、滚动及当前页; - 外链复用 `SecurityDialog`,适配层不调用桌面服务。 - -## 目录树 - -实现 `OfdDocument::outline()`,并为 OFD 开启 `PREVIEW_CATALOG`。 -按 rofd 的父子关系与源顺序转换完整目录,不依赖源文件的建议子节点数量。 -校验关系索引,遍历必须有界,不因深层目录造成无限递归。 - -目录展示不再限制为三层。没有动作、目标失效、动作不支持的节点仍保留标题 -和子树,页码栏留空,不能把 `-1` 或 `ROFD_NO_INDEX` 显示或解释为第一页。 -这些节点可以展开和选择,但选择不触发跳转。 - -首次显示使用 OFD 的 `expanded` 值;已有阅读记录中的展开状态优先。 -刷新模型、恢复展开状态、随当前页同步选中项均不能执行动作。 -用户鼠标或键盘激活每次最多执行一次;外链不能因选中项变化而自动弹窗。 - -## 目标语义与坐标 - -rofd 已解析 PageID 和命名书签。仅在 `HAS_PAGE_INDEX` 存在、索引未越界 -且模式受支持时建立可执行的页内目标;未知模式与未解析目标不执行。 -零基页码仅在兼容旧 `Link.page` 等一基字段时加一。 - -坐标先转换为目标页未旋转、未缩放的逻辑像素: - -```text -x = (left_mm - physical_page.x) * xDpi / 25.4 -y = (top_mm - physical_page.y) * yDpi / 25.4 -``` - -使用目标页的物理原点,而不是源链接所在页的原点。右、下边界做同样转换。 -缺省坐标不参与换算,不能把缺省的零减去物理原点。旋转只在视图映射时处理, -通过页面到场景的变换定位,避免重复旋转或重复应用对象 CTM。 - -执行模式: - -- `XYZ`:应用明确指定的坐标;缺省轴保留激活前视口相对当前页的位置, - 映射到目标页后受页面和滚动范围限制。缩放缺省或为零时保持当前比例; - 正值作为倍率交给 reader 的比例限制逻辑,负值或非有限值拒绝。 -- `Fit`:按目标整页与可用视口计算比例,定位目标页。 -- `FitH`:按目标页宽度适配,应用可选 Top;Top 缺省时沿用上述缺省轴策略。 -- `FitV`:按目标页高度适配,应用可选 Left;Left 缺省时沿用上述缺省轴策略。 -- `FitR`:要求四条边界均存在、有限且形成正面积矩形;适配并定位该区域。 - 不完整或退化矩形不执行,不悄悄变成整页或第一页跳转。 - -所有模式遵守 reader 当前缩放上下限,不绕过大页面的安全比例限制。 -适配比例根据目标页/目标区域计算,不直接借用基于文档最大页尺寸的全局 -适配结果。旋转和双页布局纳入可用视口与场景变换计算。 - -## 页面链接与动作策略 - -实现 `OfdPage::getLinkAtPoint()`。首次查询生成 Qt 自有的不可变链接缓存, -包含命中范围和可执行目标;空列表也作为已加载状态缓存,鼠标移动不重复 -构造快照或解析页面。初始化同步保护,失败保留失败状态并记录诊断,避免 -每次悬停重复失败和刷日志。 - -仅考虑 `CLICK` 动作。按 rofd 返回的源顺序选择第一个命中且受支持的动作, -每次激活最多执行一个,不连带执行同区域的其它动作。目录中的动作使用相同 -筛选策略。文档打开、页面打开和未知事件始终不执行。 - -一个链接的多个矩形独立参与命中,不能合并成覆盖中间空白的大包围盒。 -矩形已经包含 rofd 应用的对象与祖先变换,只做页面原点和 DPI 换算。 -命中能力以 rofd 提供的保守矩形为界,不承诺裁剪、遮挡或曲线路径的精确命中。 - -URI 先使用显式 Base 解析,再校验最终地址。首轮只允许有效的绝对 -`http`、`https`、`mailto` 地址;没有可用 Base 的相对地址不猜测为本地文件。 -`file`、其它协议、附件 GotoA 和未知动作均不可执行。 -安全确认对话框显示最终将打开的地址;用户取消时不调用外部程序。 -此限制只作用于新 OFD 导航,不改变其它格式已有的外链策略。 - -## 资源、警告与错误 - -所有 rofd 输出记录初始化 `struct_size`,遵守 `HAS_*` 和 `ROFD_NO_INDEX`。 -快照用 RAII 释放;标题、URI 和其它借用字符串在释放前复制为 Qt 自有数据。 -文档/页面所有者销毁前结束相关查询,不跨线程共享可变输出槽。 - -目录、目标页尺寸和链接查询可能触发惰性解析。完成查询后刷新现有警告接口, -复用文档警告去重与属性展示。导航失败不影响已有渲染或文本查询。 -目录与页面链接分别缓存,目录失败不能阻止 rofd 本可独立提供的链接查询。 - -## 构建与交付边界 - -更新本地构建使用的配套 rofd 头文件和共享库;CMake 增加目录、目标与链接 -符号检查,避免旧库带着相同版本号通过配置后才在链接阶段失败。 -不在源码提交中加入生成的共享库。设计时 rofd 仍标为 0.3.0;实施时上游已正式 -发布 0.4.0,发行版依赖下限相应更新为 0.4.0。 - -实现分两项审查与交付: - -1. 目录、公共导航目标与跳转执行,包括侧栏、完整树、坐标、模式及共享动作 - 安全策略;目录外链同样需要显式激活和确认。 -2. 页面链接与命中缓存,复用第一项的页内跳转及外链安全策略,并补充页面 - 点击和悬停场景的验证。 - -## 验收 - -先增加回归测试并确认未接入时失败,再实现;继续使用不链接 PDFium 的轻量 -OFD 测试目标。为目录模型和纯导航计算增加独立轻量覆盖,不构建或链接 -`test-deepin-reader` 聚合测试程序。 - -必须覆盖: - -- 空目录、无目标父节点、四层以上目录、初始展开状态和恢复状态优先级。 -- PageID 与命名书签、缺失目标、未知模式、缺省坐标、显式零、`Zoom=0`。 -- 非零物理原点、跨不同尺寸页面、五种目标模式、旋转与双页布局。 -- 单链接多个矩形、中间空白不命中、重叠链接的确定性选择、空/失败缓存。 -- 非 CLICK 事件、附件动作、URI/Base 解析、协议限制和用户取消外链确认。 -- 新增惰性导航警告,以及未设置新目标时 PDF/XPS 仍走原有路径。 - -最终只以 `cmake --build build --target deepin-reader -j1` 构建 reader 本体, -检查实际加载的 rofd 库,并用受控 OFD 做真实窗口的目录/链接交互验证。 -界面验证与后台测试分别报告,不把适配层测试通过当成用户交互已验证。