From 5b4f04b30a869396c509201367a1d5b296becf7e Mon Sep 17 00:00:00 2001 From: clover caruso Date: Sat, 12 Sep 2026 20:39:50 -0700 Subject: [PATCH] feat: render matrices, equation arrays, boxes and one-sided limits as OneNote does A third equation-editor session (corpus/math-edit/native-editor-3) adds a matrix, an equation array, upper and lower limit objects, padded and boxed formulas, a nested fraction and a sum with only an upper limit to the MathML oracle. The model carries a matrix or array's column count (MathObject::columns, text-run data 0x0c003451, written back by the equation pass), and the mapping reproduces all ten exports: mtable cells row by row, equation-array rows with alignment groups and a mark for each ampersand, mpadded and menclose boxes, and n-ary operators that leave an empty limit out (mover or msup, munder or msub). Every object kind the editor produced now has a native check. Assisted-by: claude-fable-5.1 --- corpus/math-edit/README.md | 10 ++ .../before-read/environment.json | 7 + .../native-editor-3/before-read/hierarchy.xml | 2 + .../native-editor-3/before-read/page-000.xml | 3 + .../native-editor-3/before-read/payloads.json | 3 + .../math-edit/native-editor-3/commands.jsonl | 6 + .../math-edit/native-editor-3/equations.ahk | 113 ++++++++++++++ .../math-edit/native-editor-3/equations.json | 13 ++ .../math-edit/native-editor-3/equations.png | Bin 0 -> 40561 bytes corpus/math-edit/native-editor-3/machine.json | 1 + .../notebook/Open Notebook.onetoc2 | Bin 0 -> 3764 bytes .../native-editor-3/notebook/links.one | Bin 0 -> 19696 bytes .../native-editor-3/read/environment.json | 7 + .../native-editor-3/read/hierarchy.xml | 2 + .../native-editor-3/read/page-000.xml | 4 + .../native-editor-3/read/payloads.json | 3 + corpus/math-edit/native-editor-3/run.json | 18 +++ .../native-editor-3/scripts/cold.ps1 | 27 ++++ .../native-editor-3/scripts/read.ps1 | 142 ++++++++++++++++++ corpus/math-edit/native-editor-3/source.json | 14 ++ .../math-edit/native-editor-3/teardown.json | 1 + crates/onestore/src/document.rs | 3 + crates/onestore/src/page/math.rs | 79 ++++++++-- crates/onestore/src/page/write.rs | 3 + crates/onestore/tests/page_math.rs | 11 ++ tools/test_math_edit.py | 1 + 26 files changed, 463 insertions(+), 10 deletions(-) create mode 100644 corpus/math-edit/native-editor-3/before-read/environment.json create mode 100644 corpus/math-edit/native-editor-3/before-read/hierarchy.xml create mode 100644 corpus/math-edit/native-editor-3/before-read/page-000.xml create mode 100644 corpus/math-edit/native-editor-3/before-read/payloads.json create mode 100644 corpus/math-edit/native-editor-3/commands.jsonl create mode 100644 corpus/math-edit/native-editor-3/equations.ahk create mode 100644 corpus/math-edit/native-editor-3/equations.json create mode 100644 corpus/math-edit/native-editor-3/equations.png create mode 100644 corpus/math-edit/native-editor-3/machine.json create mode 100644 corpus/math-edit/native-editor-3/notebook/Open Notebook.onetoc2 create mode 100644 corpus/math-edit/native-editor-3/notebook/links.one create mode 100644 corpus/math-edit/native-editor-3/read/environment.json create mode 100644 corpus/math-edit/native-editor-3/read/hierarchy.xml create mode 100644 corpus/math-edit/native-editor-3/read/page-000.xml create mode 100644 corpus/math-edit/native-editor-3/read/payloads.json create mode 100644 corpus/math-edit/native-editor-3/run.json create mode 100644 corpus/math-edit/native-editor-3/scripts/cold.ps1 create mode 100644 corpus/math-edit/native-editor-3/scripts/read.ps1 create mode 100644 corpus/math-edit/native-editor-3/source.json create mode 100644 corpus/math-edit/native-editor-3/teardown.json diff --git a/corpus/math-edit/README.md b/corpus/math-edit/README.md index 304e67a5ab432fa9f62be01d2a078b684b5f3b95..76b3c8eee75a54973364e4a431c410aa5fdc837a 100644 --- a/corpus/math-edit/README.md +++ b/corpus/math-edit/README.md @@ -25,6 +25,16 @@ degree second), a lower limit (19: `munder`, with the upright `lim` as one overbar (23: `mover accent="false"` with a stretchy bar) and an accent (10: `mover accent="true"` with the combining circumflex exported as `^`). +`native-editor-3/` is a third session: `\matrix(1&2@3&4)` (exported inline, +its trailing `(\matrix(a&b@c&d))` staying literal text), `\eqarray(x&=1@y&=2)`, +`x\above 2`, `x\below 2`, `\box(x)`, `\rect(x)`, `\underline(x)` (a literal +`▱` before fences), `\iint x dx dy` (a bare operator), `f(x)/(x^2+1)` and +`\sum^n x`. Its MathML adds matrices (20: `mtable` of cells row by row, the +column count on the opening run's `0x0c003451`), equation arrays (15: rows +with `maligngroup` and a `malignmark` for each `&`), an upper limit (33: +`mover`), boxes (11: `mpadded`; 12: `menclose notation="box"`) and an n-ary +operator with one empty limit (`mover`, or `msup` for integrals). + `written/candidate` is the page writer's output for `equations_are_written_and_read_back`: on a fresh section, every native equation of both fixtures copied as a new paragraph, then two expressions diff --git a/corpus/math-edit/native-editor-3/before-read/environment.json b/corpus/math-edit/native-editor-3/before-read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..17bdc24f0944680caf415698e8c1fa9dd89308c9 --- /dev/null +++ b/corpus/math-edit/native-editor-3/before-read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-22B0E73B", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/math-edit/native-editor-3/before-read/hierarchy.xml b/corpus/math-edit/native-editor-3/before-read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..1728c88c790d94b8aca20c92d9e3737d6a787245 --- /dev/null +++ b/corpus/math-edit/native-editor-3/before-read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/math-edit/native-editor-3/before-read/page-000.xml b/corpus/math-edit/native-editor-3/before-read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..29d91e34592525489edde90a4fd495470924e546 --- /dev/null +++ b/corpus/math-edit/native-editor-3/before-read/page-000.xml @@ -0,0 +1,3 @@ + +the Rust site]]> diff --git a/corpus/math-edit/native-editor-3/before-read/payloads.json b/corpus/math-edit/native-editor-3/before-read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/math-edit/native-editor-3/before-read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/math-edit/native-editor-3/commands.jsonl b/corpus/math-edit/native-editor-3/commands.jsonl new file mode 100644 index 0000000000000000000000000000000000000000..38513d47fc25a01ccdb338cb2cd4289f115d661e --- /dev/null +++ b/corpus/math-edit/native-editor-3/commands.jsonl @@ -0,0 +1,6 @@ +{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null} +{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-22B0E73B -ExpectedPages 1 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} +{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} +{"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null} +{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-22B0E73B -UseCurrentCache -ExpectedPages -1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null} +{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null} diff --git a/corpus/math-edit/native-editor-3/equations.ahk b/corpus/math-edit/native-editor-3/equations.ahk new file mode 100644 index 0000000000000000000000000000000000000000..facaa25bb9f46c12b0b0e67707cdddee0f35af63 --- /dev/null +++ b/corpus/math-edit/native-editor-3/equations.ahk @@ -0,0 +1,113 @@ +ComObject("OneNote.Application").NavigateTo("{ACFEEB7F-AA18-03BD-3300-EB94605262CF}{1}{B0}", "", false) +WinWait("ahk_exe ONENOTE.EXE", , 30) +WinActivate("ahk_exe ONENOTE.EXE") +WinWaitActive("ahk_exe ONENOTE.EXE", , 30) +WinMaximize("ahk_exe ONENOTE.EXE") +Sleep(3000) +CoordMode("Mouse", "Screen") +Click(300, 140) +Sleep(500) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(500) +Send("!=") +Sleep(600) +SendText("\matrix(1&2@3&4)") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("(\matrix(a&b@c&d))") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("\eqarray(x&=1@y&=2)") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("x\above 2") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("x\below 2") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("\box(x)") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("\rect(x)") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("\underline(x)") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("\iint x dx dy") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("f(x)/(x^2+1)") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Send("!=") +Sleep(600) +SendText("\sum^n x") +Send(" ") +Sleep(600) +Send("{End}") +Sleep(300) +Send("{Enter}") +Sleep(600) +Sleep(2000) diff --git a/corpus/math-edit/native-editor-3/equations.json b/corpus/math-edit/native-editor-3/equations.json new file mode 100644 index 0000000000000000000000000000000000000000..83fb4df3362bb2ac7e9fa657fe6db76614b54e31 --- /dev/null +++ b/corpus/math-edit/native-editor-3/equations.json @@ -0,0 +1,13 @@ +{ + "exit": 0, + "stdout": "", + "stderr": "", + "w": 800, + "h": 600, + "error": null, + "win": { + "title": "Read about Rust the Rust site - Microsoft OneNote", + "class": "Framework::CFrame", + "dialog": false + } +} \ No newline at end of file diff --git a/corpus/math-edit/native-editor-3/equations.png b/corpus/math-edit/native-editor-3/equations.png new file mode 100644 index 0000000000000000000000000000000000000000..500c3242eca41b831707f25fc362f3ccc01a50b1 GIT binary patch literal 40561 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 literal 0 HcmV?d00001 diff --git a/corpus/math-edit/native-editor-3/notebook/links.one b/corpus/math-edit/native-editor-3/notebook/links.one new file mode 100644 index 0000000000000000000000000000000000000000..84ad1c8a997f60a3ffb15dff1fdf9a05cf8ebb93 GIT binary patch literal 19696 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 + diff --git a/corpus/math-edit/native-editor-3/read/page-000.xml b/corpus/math-edit/native-editor-3/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..812bc80878b6da3405d3d981abedc93d794a3dff --- /dev/null +++ b/corpus/math-edit/native-editor-3/read/page-000.xml @@ -0,0 +1,4 @@ + +the Rust site]]>1234(\matrix(a&b@c&d)) ]]>x=1y=2]]>x2]]>x2]]>x]]>x]]>▱x]]>∬x dx dy ]]>fxx2+1]]>∑nx ]]> diff --git a/corpus/math-edit/native-editor-3/read/payloads.json b/corpus/math-edit/native-editor-3/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/math-edit/native-editor-3/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/math-edit/native-editor-3/run.json b/corpus/math-edit/native-editor-3/run.json new file mode 100644 index 0000000000000000000000000000000000000000..9e670c7143179c57e2107d7c15c06d0cb8b81a64 --- /dev/null +++ b/corpus/math-edit/native-editor-3/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/corpus/link-edit/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/math-edit/native-editor-3/scripts/cold.ps1 b/corpus/math-edit/native-editor-3/scripts/cold.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..a0b99e7aeb86f4dc10032537800501a52ad65b4f --- /dev/null +++ b/corpus/math-edit/native-editor-3/scripts/cold.ps1 @@ -0,0 +1,27 @@ +param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '') +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$root = [IO.Path]::GetFullPath($Root).TrimEnd('\') +if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { + throw 'Choose a run directly below C:\one-tests\runs.' +} +if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' } +$key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote' +if ($CloneHost) { + if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) { + throw 'The disposable clone hostname does not match this machine.' + } + New-Item "$key\Options\Paths" -Force | Out-Null + New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null +} elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or + -not (Test-Path 'C:\one-tests\profile-original-cache')) { + throw 'Park the personal OneNote profile before resetting the test cache.' +} +$cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0' +$parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root)) +if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' } +New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null +if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked } +if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse } +New-Item "$key\OpenNotebooks" | Out-Null +New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null diff --git a/corpus/math-edit/native-editor-3/scripts/read.ps1 b/corpus/math-edit/native-editor-3/scripts/read.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..476094ab1e0c47077988a6461db2080cf208642b --- /dev/null +++ b/corpus/math-edit/native-editor-3/scripts/read.ps1 @@ -0,0 +1,142 @@ +param( + [Parameter(Mandatory=$true)][string]$Root, + [int]$ExpectedPages = -1, + [switch]$UseCurrentCache, + [switch]$Pdf, + [switch]$KeepOpen, + [string]$CloneHost = '' +) +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$root = [IO.Path]::GetFullPath($Root).TrimEnd('\') +if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { + throw 'Choose a run directly below C:\one-tests\runs.' +} +$notebook = Join-Path $root 'notebook' +$output = Join-Path $root 'read' +if (Test-Path $output) { throw 'Choose a new read destination.' } +if ($UseCurrentCache) { + if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') { + throw 'Park the personal OneNote profile before reading test notebooks.' + } +} else { + & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost +} +New-Item -ItemType Directory -Path $output | Out-Null +$app = New-Object -ComObject OneNote.Application +$notebookId = '' +$failure = $null +try { + $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0) + $process = Get-Process ONENOTE + @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion; + powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } | + ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8 + $sections = @() + foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) { + $id = '' + $app.OpenHierarchy($file.FullName, '', [ref]$id, 0) + $sections += $id + } + $deadline = [DateTime]::UtcNow.AddSeconds(300) + $previous = '' + $lastChange = '' + $stableSince = [DateTime]::UtcNow + $settled = $false + do { + $pages = @{} + foreach ($section in $sections) { + $hierarchy = '' + $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) + [xml]$xml = $hierarchy + foreach ($node in $xml.SelectNodes('//*[@path]')) { + if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) { + throw 'OneNote opened a section outside the copied notebook.' + } + } + foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) { + $id = $node.GetAttribute('ID') + $content = '' + $app.GetPageContent($id, [ref]$content, 1, 1) + $pages[$id] = $content + } + } + $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] })) + if ($signature -ne $previous) { + $lastChange = $previous + $previous = $signature + $stableSince = [DateTime]::UtcNow + } + if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or + ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and + ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break } + Start-Sleep -Milliseconds 250 + } while ([DateTime]::UtcNow -lt $deadline) + if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) { + [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8) + $index = 0 + foreach ($id in @($pages.Keys | Sort-Object)) { + [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) + $index++ + } + $hierarchy = '' + $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1) + [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8) + throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled." + } + $index = 0 + $payloads = @() + foreach ($id in @($pages.Keys | Sort-Object)) { + [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) + if ($Pdf) { + $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index) + $app.NavigateTo($id, '', $false) + $app.Publish($id, $pdfPath, 3, '') + if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' } + } + [xml]$page = $pages[$id] + foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) { + $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache')) + $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant() + [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes) + $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID'); + kind = $file.LocalName; name = $file.GetAttribute('preferredName'); + sha256 = $hash; bytes = $bytes.Length } + } + $index++ + } + [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8) + $all = '' + $app.GetHierarchy($notebookId, 4, [ref]$all, 1) + [xml]$finalTree = $all + $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique) + if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) { + throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.' + } + [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8) + Write-Output "Read $($sections.Count) sections and $($pages.Count) pages." +} catch { + $failure = $_ + [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) + throw +} finally { + try { + try { + if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) } + if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) { + $app.CloseNotebook($notebookId, $false) + } + } catch { + if ($null -eq $failure) { throw } + [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) + } + } finally { + [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) + } + $app = $null + [GC]::Collect() + [GC]::WaitForPendingFinalizers() + if (-not $UseCurrentCache -and -not $CloneHost) { + Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10 + } +} diff --git a/corpus/math-edit/native-editor-3/source.json b/corpus/math-edit/native-editor-3/source.json new file mode 100644 index 0000000000000000000000000000000000000000..14786084a474f1cc0a6f522bd49c8104ad3627f0 --- /dev/null +++ b/corpus/math-edit/native-editor-3/source.json @@ -0,0 +1,14 @@ +[ + { + "path": "Open Notebook.onetoc2", + "bytes": 1604, + "sha256": "6731c4214437fe1cb6aa347b0004542b7f7e4260f47773bf9ec07c4e894de47c", + "mtime_ns": 1789206821144296891 + }, + { + "path": "links.one", + "bytes": 6128, + "sha256": "0cfb356ed374fe919f73fb73fdbe5d66e0baa484d4119f70074d9f869d6db78a", + "mtime_ns": 1789206821116200533 + } +] diff --git a/corpus/math-edit/native-editor-3/teardown.json b/corpus/math-edit/native-editor-3/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/math-edit/native-editor-3/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/crates/onestore/src/document.rs b/crates/onestore/src/document.rs index 1edbd7d627ce6c6d63bf2045d2659e32b0d27b54..d0590f7da2d8b145c496f728a84329d031f7c6f4 100644 --- a/crates/onestore/src/document.rs +++ b/crates/onestore/src/document.rs @@ -136,6 +136,8 @@ format_fields! { pub struct MathObject { pub kind: u32, pub arguments: Option, + /// Column count of a matrix or equation array. + pub columns: Option, pub symbols: Vec, } @@ -232,6 +234,7 @@ impl Revision<'_> { }) }) .transpose()?, + columns: scalar(0x0c003451).and_then(|b| b.first().copied()), symbols: [0x10003453, 0x10003454, 0x10003455] .into_iter() .filter_map(scalar) diff --git a/crates/onestore/src/page/math.rs b/crates/onestore/src/page/math.rs index a822a0b7d7fb75eb776766f9c99c83fa28b2103c..fe596d1829df6f894bd675fab62ecab097f7437c 100644 --- a/crates/onestore/src/page/math.rs +++ b/crates/onestore/src/page/math.rs @@ -1,9 +1,9 @@ //! Equations as OneNote stores them: a linear text where U+FDD0 opens an inline object, //! U+FDEE separates its arguments and U+FDEF closes it, with the object's kind on the run //! data of the opening character. `Math::parse` builds the tree and `mathml` renders it the -//! way OneNote's own export does for the kinds native fixtures have verified (superscript, -//! subscript, both, fraction, brackets, n-ary operators with limits); radicals, under/over -//! limits and the rest follow the text object model's meaning without a native check. +//! way OneNote's own export does, verified against native fixtures for every kind the +//! equation editor produced: scripts, fractions, fences, n-ary operators with any limits, +//! radicals, limit objects, accents, overbars, boxes, matrices and equation arrays. use super::text::Paragraph; use crate::document::MathObject; @@ -18,6 +18,9 @@ pub enum Math { Object { kind: u32, symbols: Vec, + /// Column count of a matrix (arguments are its cells, row by row) or an equation + /// array (arguments are its rows). + columns: Option, arguments: Vec>, }, } @@ -48,6 +51,7 @@ impl Math { struct Frame { kind: u32, symbols: Vec, + columns: Option, arguments: Vec>, } let mut frames: Vec = Vec::new(); @@ -83,6 +87,7 @@ impl Math { frames.push(Frame { kind: object.kind, symbols: object.symbols.clone(), + columns: object.columns, arguments: Vec::new(), }); sequences.push(Vec::new()); @@ -105,6 +110,7 @@ impl Math { sequences.last_mut().unwrap().push(Math::Object { kind: frame.kind, symbols: frame.symbols, + columns: frame.columns, arguments: frame.arguments, }); } @@ -147,6 +153,7 @@ impl Math { Math::Object { kind, symbols, + columns, arguments, } => { // A lone letter, number or operator stands bare; anything else is a row. @@ -176,6 +183,44 @@ impl Math { out.push_str(&format!("")); }; match (kind, arguments.len()) { + (11, 1) => wrapped(out, "mpadded", &[0]), + (12, 1) => { + out.push_str(""); + argument(out, 0); + out.push_str(""); + } + // Matrix cells row by row; an equation array's rows carry `&` alignment + // marks, exported as an alignment group at the row start and a mark at each. + (20, _) | (15, _) => { + let width = if *kind == 20 { + usize::from(columns.unwrap_or(1).max(1)) + } else { + 1 + }; + out.push_str(""); + for (r, row) in arguments.chunks(width).enumerate() { + out.push_str(""); + for (c, cell) in row.iter().enumerate() { + out.push_str(""); + if *kind == 15 { + out.push_str(""); + for node in cell { + match node { + Math::Operator('&') => { + out.push_str("") + } + node => node.write(out), + } + } + } else { + argument(out, r * width + c); + } + out.push_str(""); + } + out.push_str(""); + } + out.push_str(""); + } (31, 2) => wrapped(out, "msup", &[0, 1]), (29, 2) => wrapped(out, "msub", &[0, 1]), (30, 3) => wrapped(out, "msubsup", &[0, 1, 2]), @@ -220,19 +265,29 @@ impl Math { out.push_str(""); } // Lower limit, upper limit, body; integrals take their limits as scripts, - // other operators above and below. + // other operators above and below, and an empty limit leaves its side out. (21, 3) => { let operator = symbols.first().copied().unwrap_or('∑'); - let element = if ('\u{222b}'..='\u{2233}').contains(&operator) { - "msubsup" - } else { - "munderover" + let scripts = ('\u{222b}'..='\u{2233}').contains(&operator); + let (lower, upper) = (!arguments[0].is_empty(), !arguments[1].is_empty()); + let element = match (lower, upper, scripts) { + (true, true, true) => "msubsup", + (true, true, false) => "munderover", + (true, false, true) => "msub", + (true, false, false) => "munder", + (false, true, true) => "msup", + (false, true, false) => "mover", + (false, false, _) => "mrow", }; out.push_str(&format!( "{operator}" )); - argument(out, 0); - argument(out, 1); + if lower { + argument(out, 0); + } + if upper { + argument(out, 1); + } out.push_str(&format!("")); out.push_str(""); for node in &arguments[2] { @@ -320,6 +375,7 @@ impl Math { format.math_object = Some(object.unwrap_or(MathObject { kind: PLAIN_RUN, arguments: None, + columns: None, symbols: Vec::new(), })); (text, format) @@ -341,6 +397,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option)>) Math::Object { kind, symbols, + columns, arguments, } => { if !leaf.is_empty() { @@ -351,6 +408,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option)>) Some(MathObject { kind: *kind, arguments: Some(arguments.len() as u32), + columns: *columns, symbols: symbols.clone(), }), )); @@ -365,6 +423,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option)>) let object = MathObject { kind: *kind, arguments: (index > 0).then_some(index as u32), + columns: None, symbols: Vec::new(), }; let extended = runs.len() > before; diff --git a/crates/onestore/src/page/write.rs b/crates/onestore/src/page/write.rs index 8c2285ac616c3957dbf7d5a44981a2e8c51f70b2..8cb3217bf672b5da8e43c65ec37a6a45bb12d9b5 100644 --- a/crates/onestore/src/page/write.rs +++ b/crates/onestore/src/page/write.rs @@ -1770,6 +1770,9 @@ impl Lowering<'_> { if let Some(count) = object.arguments { set.push((0x14003450, count.to_le_bytes().to_vec())); } + if let Some(columns) = object.columns { + set.push((0x0c003451, vec![columns])); + } for (id, symbol) in [0x10003453, 0x10003454, 0x10003455] .into_iter() .zip(&object.symbols) diff --git a/crates/onestore/tests/page_math.rs b/crates/onestore/tests/page_math.rs index 45e4b4c35ce028c7eb5a84f1abb3229c04fb1ba6..be3249abf1cbe63c07a12e33ec5229c14dd8ebc0 100644 --- a/crates/onestore/tests/page_math.rs +++ b/crates/onestore/tests/page_math.rs @@ -18,6 +18,12 @@ const EDITOR_2: &[u8] = include_bytes!("../../../corpus/math-edit/native-editor-2/notebook/links.one"); const EDITOR_2_READ: &str = include_str!("../../../corpus/math-edit/native-editor-2/read/page-000.xml"); +/// A third session: a matrix, an equation array, upper and lower limit objects, boxes, a +/// nested fraction and a sum with only an upper limit. +const EDITOR_3: &[u8] = + include_bytes!("../../../corpus/math-edit/native-editor-3/notebook/links.one"); +const EDITOR_3_READ: &str = + include_str!("../../../corpus/math-edit/native-editor-3/read/page-000.xml"); fn native_mathml(read: &str) -> Vec { read.split(">) -> Math { Math::Object { kind, symbols: symbols.chars().collect(), + columns: None, arguments, } } diff --git a/tools/test_math_edit.py b/tools/test_math_edit.py index 83227267fa7834792960f48dc92b32a35d597c97..63bd9370a4884f79ceb5adf5cdd1a38516c5fac2 100644 --- a/tools/test_math_edit.py +++ b/tools/test_math_edit.py @@ -23,6 +23,7 @@ class MathEditTest(unittest.TestCase): def test_equation_editor_expressions_export_as_mathml(self): self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor/read/page-000.xml')), 5) self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor-2/read/page-000.xml')), 9) + self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor-3/read/page-000.xml')), 10) def test_rust_written_equations_export_the_expected_mathml(self): with TemporaryDirectory() as temporary: -- 2.54.0