From 0ce468a3ad381e0cf888679a86e3f14cb2147d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 9 Oct 2026 03:20:07 -0400 Subject: [PATCH] ElyTable: under LOK an AutoFilter range keeps its rows through list, apply, toggle, undo and reopen (R2.9) --- agents.md | 2 +- crates/ely-lok/src/client/tests/filter.rs | 77 ++++++++++-- ...calc-autofilter-keeps-a-marked-block.patch | 110 ------------------ ...0-lok-calc-autofilter-keeps-its-rows.patch | 65 +++++++++++ tasks/reviews/R2.9-r1.md | 7 ++ 5 files changed, 141 insertions(+), 120 deletions(-) delete mode 100644 patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch create mode 100644 patches/libreoffice/0040-lok-calc-autofilter-keeps-its-rows.patch create mode 100644 tasks/reviews/R2.9-r1.md diff --git a/agents.md b/agents.md index 519546a..978e60a 100644 --- a/agents.md +++ b/agents.md @@ -263,4 +263,4 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely - 2026-10-09(用户定:“打开方式”里不得出现 LibreOffice):系统会登记磁盘上能找到的 .app、挂载镜像里的 .app 与运行过的包,构建目录里的 `LibreOffice.app` 与测试挂载的官方镜像曾因此成为 xlsx 等的默认程序。现构建后由 `scripts/inert-lo.sh` 去掉其全部文件与 URL 声明、快速查看与聚焦插件,注销它,并在 `engines/` 放 `.metadata_never_index`;Ely 只取其 Frameworks 与 Resources。任何脚本与测试都不挂载 LibreOffice 镜像:LOK 探测用本构建的内核,单测所需的官方内核由 `scripts/extract-official-lo.sh` 以 7zz 解成无包的文件夹。`tests/scripts/lo-unregistered.sh` 以 NSWorkspace 核对 docx、xlsx、pptx、odt、csv 的“打开方式”里没有 LibreOffice。 - 2026-10-09(用户报:ElyTable 点格跳到别处、拖不出选区):Calc 在 `sc_print_twips_msgs` 下以打印 twips(行高原值)报光标、选区与 `.uno:SheetGeometryData`,图块与鼠标却在图块 twips(每行按 `ScViewData::ToPixel` 截成整像素),两者随行号漂移(5000 行处约 140 行)。视图对外一律打印 twips;`LokView::set_geometry` 收下工作表几何后,`sheet::Axis::tile`、`print` 照内核的截断换算(每像素 twips 取图块的像素与 twips 之比),绘制、显示、跳转与冻结都先换成图块 twips,鼠标以图块 twips 发出(`tile_point`),拖动句柄仍为打印 twips;`.uno:` 读回与应用(表头、填充柄)照旧用打印 twips,表头的起点取 `LokView::scrolled_start`。内核读鼠标用的比例原取最后一块图块,worker 现于每次 `setClientVisibleArea` 前以 `setClientZoom` 告知图块比例,点击不再依赖先画过图块。 - 2026-10-09(用户报:ElyPDF 改字报“这种字体画不出 V”):文档自带的子集字体常无可用的 Unicode 表(LibreOffice 导出的子集表为占位,0x00 起逐一对应字形号),按 Unicode 找字形一律失败。现以“文档自带字体”写字时直接用页面上的那个字体对象(`shim/ely_pen.c`;`pdf_load_font` 按对象缓存,与文字层的字体同一对象),以它自己的编码写:按文字提取的读法(ToUnicode 或 cid_to_ucs)倒出 Unicode 到编码的表,排版取它的宽度(`pdf_lookup_hmtx`),原有的 ToUnicode 与宽度不动,两个字共用一个字形时各写各的编码(原先按字形号写、重建 ToUnicode,共用字形的字被改写,且 CMap 漏写条目数,PDFKit 读不出字)。删去原文字块后页面资源里可能已无该字体,故按对象号以新名字重新引用。只支持编码即 CID 的字体(简单字体与 Identity-H),其余拒绝并说明;页面上没有的字仍报缺字。排版按同一套宽度量出的框,比较时留 0.01 点的余量(原块宽度即这些宽度之和,浮点误差曾使末字折行)。 -- 2026-10-09:ElyTable 的自动筛选(R2.9)。内核每次打开筛选列表都把未命名的筛选区向下扩到紧邻的数据行(为接上后来追加的行),分不出选区时就在下方的合计行,合计因此进了列表、随数据一起被隐藏。补丁 `0040-lok-calc-autofilter-keeps-a-marked-block.patch`:LOK 下对跨行的选区开自动筛选时,筛选区记下“选定的行”(不存入文件),此后不再扩展;单格开筛选照旧取整个数据区。已知限制:存后重开的文件不带此记号,其筛选区照内核原样扩展。 +- 2026-10-09:ElyTable 的自动筛选(R2.9)。内核会重新推算未命名的自动筛选区:每次打开筛选列表时向下扩到紧邻的数据行(`ExtendDataArea`、`ExtendBackColorArea`,为接上后来追加的行),命令从单格找到它时按连续数据区重算(`ScDocShell::GetDBData`:应用条件、关闭筛选及其撤销);选区下方紧邻的合计行因此进了列表、随数据一起被隐藏,存后重开的筛选区同样会扩。补丁 `0040-lok-calc-autofilter-keeps-its-rows.patch`:LOK 下已有的自动筛选区保持设定时的行(同 Excel),不再隐式扩展;单格开筛选时仍取整个数据区。代价:紧贴在筛选区下方追加的行不自动并入,需重新选定后再开筛选。 diff --git a/crates/ely-lok/src/client/tests/filter.rs b/crates/ely-lok/src/client/tests/filter.rs index c7cb3cf..353163e 100644 --- a/crates/ely-lok/src/client/tests/filter.rs +++ b/crates/ely-lok/src/client/tests/filter.rs @@ -1,4 +1,4 @@ -//! An AutoFilter keeps to the marked block (TABLE-08). +//! An AutoFilter range keeps its rows (TABLE-08). use super::sheet::open_book; use super::*; @@ -18,14 +18,33 @@ const BLOCK: &str = r#" "#; -/// The filter entries after an AutoFilter on `marked`. -fn entries_after(marked: &str) -> Vec { - let (_dir, mut worker, doc) = open_book(BLOCK); +/// Column A's filter entries. +fn entries(worker: &mut Worker, doc: &Doc) -> Vec { + let query = sheet::cell_query(sheet::FILTER, 0, 0); + let json = worker.values(doc, &query).expect("the entries"); + sheet::filter_values(&json).expect("parses").entries.into_iter().map(|e| e.text).collect() +} + +/// A book with an AutoFilter set on `marked`. +fn filtered(marked: &str) -> (tempfile::TempDir, Worker, Doc) { + let (dir, mut worker, doc) = open_book(BLOCK); worker.run(&doc, "cell.goto", &[marked]).expect("marks the cells"); worker.run(&doc, "data.autofilter", &[]).expect("filters"); - let query = sheet::cell_query(sheet::FILTER, 0, 0); - let json = worker.values(&doc, &query).expect("the entries"); - sheet::filter_values(&json).expect("parses").entries.into_iter().map(|e| e.text).collect() + (dir, worker, doc) +} + +/// Keeps 1: the saved range, and whether the total hides. +fn keep_one(worker: &mut Worker, doc: &Doc, dir: &std::path::Path) -> (String, bool) { + worker.run(doc, "cell.goto", &["A1"]).expect("A1"); + let one = r#"{"column":0,"conditions":[{"op":"values","values":["1"]}]}"#; + worker.run(doc, "data.filter", &[one]).expect("keeps 1"); + let out = dir.join("kept.fods"); + doc.save_as(worker, &crate::file_url(&out), "fods").expect("saves"); + let xml = std::fs::read_to_string(&out).expect("the saved book"); + let range = xml.split("table:target-range-address=\"").nth(1).expect("a filter range"); + let total = &xml[..xml.find("office:value=\"6\"").expect("the total")]; + let row = &total[total.rfind(" maTableColumnNames; ///< names of table columns -@@ -244,6 +245,9 @@ public: - bool HasAutoFilter() const { return bAutoFilter; } - void SetAutoFilter(bool bSet) { bAutoFilter = bSet; } - -+ bool HasMarkedRows() const { return bMarkedRows; } -+ void SetMarkedRows(bool bSet) { bMarkedRows = bSet; } -+ - bool IsModified() const { return bModified; } - void SetModified(bool bMod) { bModified = bMod; } - -diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx -index b1d714c55..e7c5a16c1 100644 ---- a/sc/source/core/tool/dbdata.cxx -+++ b/sc/source/core/tool/dbdata.cxx -@@ -75,6 +75,7 @@ ScDBData::ScDBData( const OUString& rName, - bDBSelection(false), - nIndex (0), - bAutoFilter (false), -+ bMarkedRows (false), - bModified (false), - mbTableColumnNamesDirty(true), - nFilteredRowCount(SCSIZE_MAX) -@@ -110,6 +111,7 @@ ScDBData::ScDBData( const ScDBData& rData ) : - bDBSelection (rData.bDBSelection), - nIndex (rData.nIndex), - bAutoFilter (rData.bAutoFilter), -+ bMarkedRows (rData.bMarkedRows), - bModified (rData.bModified), - maTableColumnNames (rData.maTableColumnNames), - maTableColumnAttributes(rData.maTableColumnAttributes), -@@ -146,6 +148,7 @@ ScDBData::ScDBData( const OUString& rName, const ScDBData& rData ) : - bDBSelection (rData.bDBSelection), - nIndex (rData.nIndex), - bAutoFilter (rData.bAutoFilter), -+ bMarkedRows (rData.bMarkedRows), - bModified (rData.bModified), - maTableColumnNames (rData.maTableColumnNames), - maTableColumnAttributes(rData.maTableColumnAttributes), -@@ -192,6 +195,7 @@ ScDBData& ScDBData::operator= (const ScDBData& rData) - bDBSelection = rData.bDBSelection; - nIndex = rData.nIndex; - bAutoFilter = rData.bAutoFilter; -+ bMarkedRows = rData.bMarkedRows; - nFilteredRowCount = rData.nFilteredRowCount; - - if (bHeaderRangeDiffers) -@@ -646,6 +650,8 @@ bool ScDBData::UpdateReference(const ScDocument& rDoc, UpdateRefMode eUpdateRefM - - void ScDBData::ExtendDataArea(const ScDocument& rDoc) - { -+ if (bMarkedRows) -+ return; - // Extend the DB area to include data rows immediately below. - SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; - SCROW nOldEndRow = nEndRow; -@@ -665,6 +671,8 @@ void ScDBData::ExtendDataArea(const ScDocument& rDoc) - - void ScDBData::ExtendBackColorArea(const ScDocument& rDoc) - { -+ if (bMarkedRows) -+ return; - // Extend the DB area to include data rows immediately below. - SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; - SCROW nOldEndRow = nEndRow; -diff --git a/sc/source/ui/view/dbfunc.cxx b/sc/source/ui/view/dbfunc.cxx -index db3d23d86..8140ce4d0 100644 ---- a/sc/source/ui/view/dbfunc.cxx -+++ b/sc/source/ui/view/dbfunc.cxx -@@ -18,6 +18,7 @@ - */ - - #include -+#include - #include - #include - #include -@@ -286,6 +287,11 @@ void ScDBFunc::ToggleAutoFilter() - return; - - pDBData->SetByRow( true ); //! undo, retrieve beforehand ?? -+ // LOK: marked rows are the range, as in Excel. -+ ScRange aMarked; -+ pDBData->SetMarkedRows(comphelper::LibreOfficeKit::isActive() -+ && rViewData.GetSimpleArea(aMarked) == SC_MARK_SIMPLE -+ && aMarked.aStart.Row() != aMarked.aEnd.Row()); - pDBData->GetQueryParam( aParam ); - - SCCOL nCol; diff --git a/patches/libreoffice/0040-lok-calc-autofilter-keeps-its-rows.patch b/patches/libreoffice/0040-lok-calc-autofilter-keeps-its-rows.patch new file mode 100644 index 0000000..b6b87ee --- /dev/null +++ b/patches/libreoffice/0040-lok-calc-autofilter-keeps-its-rows.patch @@ -0,0 +1,65 @@ +LOK Calc: an AutoFilter range keeps its rows + +Calc regrows an unnamed AutoFilter range from the data around it: each +time the filter list opens (ExtendDataArea, ExtendBackColorArea, so +appended rows join) and whenever a command finds it from a single cell +(ScDocShell::GetDBData, so applying a condition, toggling it off, and +its undo). A totals row right under a marked block joined the filter, +showed in its list and hid with the data; a saved range reopened grew +the same way. Under LOK the range keeps the rows it was set on, as in +Excel. A single cell still takes the data area when the filter is set. + +diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx +index b1d714c55..9a01847ee 100644 +--- a/sc/source/core/tool/dbdata.cxx ++++ b/sc/source/core/tool/dbdata.cxx +@@ -19,6 +19,7 @@ + + #include + #include ++#include + + #include + #include +@@ -646,6 +647,9 @@ bool ScDBData::UpdateReference(const ScDocument& rDoc, UpdateRefMode eUpdateRefM + + void ScDBData::ExtendDataArea(const ScDocument& rDoc) + { ++ // LOK: an AutoFilter range keeps its rows, as in Excel. ++ if (comphelper::LibreOfficeKit::isActive() && bAutoFilter) ++ return; + // Extend the DB area to include data rows immediately below. + SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; + SCROW nOldEndRow = nEndRow; +@@ -665,6 +669,8 @@ void ScDBData::ExtendDataArea(const ScDocument& rDoc) + + void ScDBData::ExtendBackColorArea(const ScDocument& rDoc) + { ++ if (comphelper::LibreOfficeKit::isActive() && bAutoFilter) ++ return; + // Extend the DB area to include data rows immediately below. + SCCOL nOldCol1 = nStartCol, nOldCol2 = nEndCol; + SCROW nOldEndRow = nEndRow; +diff --git a/sc/source/ui/docshell/docsh5.cxx b/sc/source/ui/docshell/docsh5.cxx +index d154597c3..54a2344af 100644 +--- a/sc/source/ui/docshell/docsh5.cxx ++++ b/sc/source/ui/docshell/docsh5.cxx +@@ -19,6 +19,7 @@ + + #include + #include ++#include + + #include + +@@ -147,7 +148,9 @@ ScDBData* ScDocShell::GetDBData( const ScRange& rMarked, ScGetDBMode eMode, ScGe + if (!bSelected) + { + bUseThis = true; +- if ( bIsNoName && (eMode == SC_DB_MAKE || eMode == SC_DB_AUTOFILTER) ) ++ // LOK: an AutoFilter range keeps its rows, as in Excel. ++ const bool bKeep = comphelper::LibreOfficeKit::isActive() && pData->HasAutoFilter(); ++ if ( bIsNoName && !bKeep && (eMode == SC_DB_MAKE || eMode == SC_DB_AUTOFILTER) ) + { + // If nothing marked or only one row marked, adapt + // "unnamed" to contiguous area. diff --git a/tasks/reviews/R2.9-r1.md b/tasks/reviews/R2.9-r1.md new file mode 100644 index 0000000..c5d9d8f --- /dev/null +++ b/tasks/reviews/R2.9-r1.md @@ -0,0 +1,7 @@ +FAIL + +patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch:73:固定选区只拦截 ExtendDataArea / ExtendBackColorArea,应用筛选条件仍可通过 ScDocShell::GetDBData 扩区。复现:使用 filter.rs 的 BLOCK,选择 A1:A4 开自动筛选,再点选 A1;读取 AutoFilterValues 得到 ["1", "2", "3"],随后执行 data.filter,参数为 {"column":0,"conditions":[{"op":"values","values":["1"]}]}。导出 ODS 的 database-range 已为 S.A1:S.A5,值为 6 的合计行带 table:visibility="filter"。GetDBData 在 sc/source/ui/docshell/docsh5.cxx:150 对单格重新计算连续数据区,172 行直接 SetArea,绕过新增守卫。同样,单格处关闭自动筛选再撤销,也恢复为包含合计行的范围。修复方向:所有重算已有筛选区的入口都遵守固定边界,切换和撤销保留原范围及其固定状态;补充单格处应用条件、关闭再撤销后的实际行可见性回归。独立 cargo build -p ely-lok --bins --locked、两个 client::tests::filter 测试及八个 client::tests::rules 测试通过,现有测试仅在初始选区仍存在时读取列表,漏掉此失败路径。证据:/tmp/ely-r29-cursor.log、/tmp/ely-r29-confirm.log。 + +patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch:106:选区判定漏掉 SC_MARK_SIMPLE_FILTERED,已有条件筛选的连续选区无法获得固定边界。复现:同一 BLOCK,选择 A1:A4,执行 data.filter,参数为 {"conditions":[{"field":0,"op":"equals","value":"1"}]};再次选择 A1:A4 并执行 data.autofilter。AutoFilterValues 随即返回 ["1", "2", "3", "6"];应用仅保留 1 的条件后,导出的筛选区为 S.A1:S.A5,合计行 6 被隐藏。GetDBData 已支持 SC_MARK_SIMPLE_FILTERED,新增标记判定却只接受 SC_MARK_SIMPLE。修复方向:对跨行的连续已筛选选区同样记录固定边界,并增加先条件筛选、再开启自动筛选的回归。该场景以独立 worker 重跑两次结果一致,证据:/tmp/ely-r29-confirm.log、/tmp/ely-r29-cursor.log。 + +patches/libreoffice/0040-lok-calc-autofilter-keeps-a-marked-block.patch:19:固定范围依赖未持久化的 bMarkedRows,XLSX / ODS 保存重开后恢复原缺陷。复现:选择同一 BLOCK 的 A1:A4 开自动筛选,保存并关闭;独立读取存档确认 XLSX 的 autoFilter ref="A1:A4"、ODS 的 target-range-address="S.A1:S.A4"。重开后首次读取筛选列表返回 ["1", "2", "3", "6"];仅保留 1 后导出,范围变成 S.A1:S.A5,合计行带 table:visibility="filter"。两种格式均复现,违反 R2.9 的选区边界及 TABLE-01 / TABLE-13 的保存使用路径;agents.md:266 记录了此限制。修复方向:保存重开后保留或从已保存的筛选范围恢复固定边界,覆盖 XLSX、ODS 往返后的列表和实际隐藏行。证据:/tmp/ely-r29-confirm.log。