| author | |
| committer | |
| log | 5b4f04b30a869396c509201367a1d5b296becf7e |
| tree | 938f3851cd9eec52cf477cbebd99caf1aad15532 |
| parent | bdb38ac1f1a4e8f65b558380079eeab79033b2f8 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
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.126 files changed, 463 insertions(+), 10 deletions(-)
corpus/math-edit/README.md+10| ... | ... | @@ -25,6 +25,16 @@ degree second), a lower limit (19: `munder`, with the upright `lim` as one |
| 25 | 25 | overbar (23: `mover accent="false"` with a stretchy bar) and an accent (10: |
| 26 | 26 | `mover accent="true"` with the combining circumflex exported as `^`). |
| 27 | 27 | |
| 28 | `native-editor-3/` is a third session: `\matrix(1&2@3&4)` (exported inline, | |
| 29 | its trailing `(\matrix(a&b@c&d))` staying literal text), `\eqarray(x&=1@y&=2)`, | |
| 30 | `x\above 2`, `x\below 2`, `\box(x)`, `\rect(x)`, `\underline(x)` (a literal | |
| 31 | `▱` before fences), `\iint x dx dy` (a bare operator), `f(x)/(x^2+1)` and | |
| 32 | `\sum^n x`. Its MathML adds matrices (20: `mtable` of cells row by row, the | |
| 33 | column count on the opening run's `0x0c003451`), equation arrays (15: rows | |
| 34 | with `maligngroup` and a `malignmark` for each `&`), an upper limit (33: | |
| 35 | `mover`), boxes (11: `mpadded`; 12: `menclose notation="box"`) and an n-ary | |
| 36 | operator with one empty limit (`mover`, or `msup` for integrals). | |
| 37 | ||
| 28 | 38 | `written/candidate` is the page writer's output for |
| 29 | 39 | `equations_are_written_and_read_back`: on a fresh section, every native |
| 30 | 40 | equation of both fixtures copied as a new paragraph, then two expressions |
corpus/math-edit/native-editor-3/before-read/environment.json created+7| ... | ... | @@ -0,0 +1,7 @@ |
| 1 | { | |
| 2 | "powershell": "5.1.14409.1005", | |
| 3 | "schema": "xs2010", | |
| 4 | "hostname": "ONE-M6-22B0E73B", | |
| 5 | "cold": true, | |
| 6 | "onenote": "14.0.4763.1000" | |
| 7 | } |
corpus/math-edit/native-editor-3/before-read/hierarchy.xml created+2| ... | ... | @@ -0,0 +1,2 @@ |
| 1 | <?xml version="1.0"?> | |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{87171530-19E5-4EC9-B43B-5CF46C9E566F}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T03:24:10.000Z" color="#B49EDE"><one:Section name="links" ID="{0DF32DF6-C20F-0C80-25AB-CBF498B1EB34}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-13T03:24:10.000Z" color="#8AA8E4"><one:Page ID="{ACFEEB7F-AA18-03BD-3300-EB94605262CF}{1}{B0}" name="Read about Rust HYPERLINK &quot;https://example.invalid/rust&quot;the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:24:10.000Z" pageLevel="1"/></one:Section></one:Notebook> |
corpus/math-edit/native-editor-3/before-read/page-000.xml created+3| ... | ... | @@ -0,0 +1,3 @@ |
| 1 | <?xml version="1.0"?> | |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{ACFEEB7F-AA18-03BD-3300-EB94605262CF}{1}{B0}" name="Read about Rust HYPERLINK &quot;https://example.invalid/rust&quot;the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:24:10.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T09:49:16.000Z" objectID="{0DF32DF6-C20F-0C80-25AB-CBF498B1EB34}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="198.3904571533203" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-12T09:49:16.000Z" objectID="{0DF32DF6-C20F-0C80-25AB-CBF498B1EB34}{24}{B0}" alignment="left"><one:T><![CDATA[Read about Rust <a | |
| 3 | href="https://example.invalid/rust">the Rust site</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/math-edit/native-editor-3/before-read/payloads.json created+3| ... | ... | @@ -0,0 +1,3 @@ |
| 1 | [ | |
| 2 | ||
| 3 | ] | |
| \ No newline at end of file |
corpus/math-edit/native-editor-3/commands.jsonl created+6| ... | ... | @@ -0,0 +1,6 @@ |
| 1 | {"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} | |
| 2 | {"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} | |
| 3 | {"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} | |
| 4 | {"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null} | |
| 5 | {"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} | |
| 6 | {"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} |
corpus/math-edit/native-editor-3/equations.ahk created+113| ... | ... | @@ -0,0 +1,113 @@ |
| 1 | ComObject("OneNote.Application").NavigateTo("{ACFEEB7F-AA18-03BD-3300-EB94605262CF}{1}{B0}", "", false) | |
| 2 | WinWait("ahk_exe ONENOTE.EXE", , 30) | |
| 3 | WinActivate("ahk_exe ONENOTE.EXE") | |
| 4 | WinWaitActive("ahk_exe ONENOTE.EXE", , 30) | |
| 5 | WinMaximize("ahk_exe ONENOTE.EXE") | |
| 6 | Sleep(3000) | |
| 7 | CoordMode("Mouse", "Screen") | |
| 8 | Click(300, 140) | |
| 9 | Sleep(500) | |
| 10 | Send("{End}") | |
| 11 | Sleep(300) | |
| 12 | Send("{Enter}") | |
| 13 | Sleep(500) | |
| 14 | Send("!=") | |
| 15 | Sleep(600) | |
| 16 | SendText("\matrix(1&2@3&4)") | |
| 17 | Send(" ") | |
| 18 | Sleep(600) | |
| 19 | Send("{End}") | |
| 20 | Sleep(300) | |
| 21 | Send("{Enter}") | |
| 22 | Sleep(600) | |
| 23 | Send("!=") | |
| 24 | Sleep(600) | |
| 25 | SendText("(\matrix(a&b@c&d))") | |
| 26 | Send(" ") | |
| 27 | Sleep(600) | |
| 28 | Send("{End}") | |
| 29 | Sleep(300) | |
| 30 | Send("{Enter}") | |
| 31 | Sleep(600) | |
| 32 | Send("!=") | |
| 33 | Sleep(600) | |
| 34 | SendText("\eqarray(x&=1@y&=2)") | |
| 35 | Send(" ") | |
| 36 | Sleep(600) | |
| 37 | Send("{End}") | |
| 38 | Sleep(300) | |
| 39 | Send("{Enter}") | |
| 40 | Sleep(600) | |
| 41 | Send("!=") | |
| 42 | Sleep(600) | |
| 43 | SendText("x\above 2") | |
| 44 | Send(" ") | |
| 45 | Sleep(600) | |
| 46 | Send("{End}") | |
| 47 | Sleep(300) | |
| 48 | Send("{Enter}") | |
| 49 | Sleep(600) | |
| 50 | Send("!=") | |
| 51 | Sleep(600) | |
| 52 | SendText("x\below 2") | |
| 53 | Send(" ") | |
| 54 | Sleep(600) | |
| 55 | Send("{End}") | |
| 56 | Sleep(300) | |
| 57 | Send("{Enter}") | |
| 58 | Sleep(600) | |
| 59 | Send("!=") | |
| 60 | Sleep(600) | |
| 61 | SendText("\box(x)") | |
| 62 | Send(" ") | |
| 63 | Sleep(600) | |
| 64 | Send("{End}") | |
| 65 | Sleep(300) | |
| 66 | Send("{Enter}") | |
| 67 | Sleep(600) | |
| 68 | Send("!=") | |
| 69 | Sleep(600) | |
| 70 | SendText("\rect(x)") | |
| 71 | Send(" ") | |
| 72 | Sleep(600) | |
| 73 | Send("{End}") | |
| 74 | Sleep(300) | |
| 75 | Send("{Enter}") | |
| 76 | Sleep(600) | |
| 77 | Send("!=") | |
| 78 | Sleep(600) | |
| 79 | SendText("\underline(x)") | |
| 80 | Send(" ") | |
| 81 | Sleep(600) | |
| 82 | Send("{End}") | |
| 83 | Sleep(300) | |
| 84 | Send("{Enter}") | |
| 85 | Sleep(600) | |
| 86 | Send("!=") | |
| 87 | Sleep(600) | |
| 88 | SendText("\iint x dx dy") | |
| 89 | Send(" ") | |
| 90 | Sleep(600) | |
| 91 | Send("{End}") | |
| 92 | Sleep(300) | |
| 93 | Send("{Enter}") | |
| 94 | Sleep(600) | |
| 95 | Send("!=") | |
| 96 | Sleep(600) | |
| 97 | SendText("f(x)/(x^2+1)") | |
| 98 | Send(" ") | |
| 99 | Sleep(600) | |
| 100 | Send("{End}") | |
| 101 | Sleep(300) | |
| 102 | Send("{Enter}") | |
| 103 | Sleep(600) | |
| 104 | Send("!=") | |
| 105 | Sleep(600) | |
| 106 | SendText("\sum^n x") | |
| 107 | Send(" ") | |
| 108 | Sleep(600) | |
| 109 | Send("{End}") | |
| 110 | Sleep(300) | |
| 111 | Send("{Enter}") | |
| 112 | Sleep(600) | |
| 113 | Sleep(2000) |
corpus/math-edit/native-editor-3/equations.json created+13| ... | ... | @@ -0,0 +1,13 @@ |
| 1 | { | |
| 2 | "exit": 0, | |
| 3 | "stdout": "", | |
| 4 | "stderr": "", | |
| 5 | "w": 800, | |
| 6 | "h": 600, | |
| 7 | "error": null, | |
| 8 | "win": { | |
| 9 | "title": "Read about Rust the Rust site - Microsoft OneNote", | |
| 10 | "class": "Framework::CFrame", | |
| 11 | "dialog": false | |
| 12 | } | |
| 13 | } | |
| \ No newline at end of file |
corpus/math-edit/native-editor-3/equations.png created| Binary files /dev/null and b/corpus/math-edit/native-editor-3/equations.png differ |
corpus/math-edit/native-editor-3/machine.json created+1| ... | ... | @@ -0,0 +1 @@ |
| 1 | {"name": "m6-22b0e73b", "hostname": "ONE-M6-22B0E73B"} |
corpus/math-edit/native-editor-3/notebook/Open Notebook.onetoc2 created| Binary files /dev/null and b/corpus/math-edit/native-editor-3/notebook/Open Notebook.onetoc2 differ |
corpus/math-edit/native-editor-3/notebook/links.one created| Binary files /dev/null and b/corpus/math-edit/native-editor-3/notebook/links.one differ |
corpus/math-edit/native-editor-3/read/environment.json created+7| ... | ... | @@ -0,0 +1,7 @@ |
| 1 | { | |
| 2 | "powershell": "5.1.14409.1005", | |
| 3 | "schema": "xs2010", | |
| 4 | "hostname": "ONE-M6-22B0E73B", | |
| 5 | "cold": false, | |
| 6 | "onenote": "14.0.4763.1000" | |
| 7 | } |
corpus/math-edit/native-editor-3/read/hierarchy.xml created+2| ... | ... | @@ -0,0 +1,2 @@ |
| 1 | <?xml version="1.0"?> | |
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{87171530-19E5-4EC9-B43B-5CF46C9E566F}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T03:26:14.000Z" color="#B49EDE" isCurrentlyViewed="true"><one:Section name="links" ID="{0DF32DF6-C20F-0C80-25AB-CBF498B1EB34}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-13T03:26:14.000Z" color="#8AA8E4" isCurrentlyViewed="true"><one:Page ID="{ACFEEB7F-AA18-03BD-3300-EB94605262CF}{1}{B0}" name="Read about Rust the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:26:14.000Z" pageLevel="1" isCurrentlyViewed="true"/></one:Section></one:Notebook> |
corpus/math-edit/native-editor-3/read/page-000.xml created+4| ... | ... | @@ -0,0 +1,4 @@ |
| 1 | <?xml version="1.0"?> | |
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{ACFEEB7F-AA18-03BD-3300-EB94605262CF}{1}{B0}" name="Read about Rust the Rust site" dateTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:26:15.000Z" pageLevel="1" isCurrentlyViewed="true" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-13T03:26:14.000Z" objectID="{0DF32DF6-C20F-0C80-25AB-CBF498B1EB34}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="198.3904571533203" height="221.7999877929687"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T09:49:16.000Z" lastModifiedTime="2026-09-13T03:25:48.000Z" objectID="{0DF32DF6-C20F-0C80-25AB-CBF498B1EB34}{24}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Read about Rust <a | |
| 3 | href="https://example.invalid/rust">the Rust site</a>]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:25:53.000Z" lastModifiedTime="2026-09-13T03:25:55.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{12}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mtable><mml:mtr><mml:mtd><mml:mn>1</mml:mn></mml:mtd><mml:mtd><mml:mn>2</mml:mn></mml:mtd></mml:mtr><mml:mtr><mml:mtd><mml:mn>3</mml:mn></mml:mtd><mml:mtd><mml:mn>4</mml:mn></mml:mtd></mml:mtr></mml:mtable></mml:math><![endif]--><span | |
| 4 | style='font-family:Calibri' lang=en-US>(\matrix(a&amp;b@c&amp;d)) </span>]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:25:57.000Z" lastModifiedTime="2026-09-13T03:25:57.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{19}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mtable><mml:mtr><mml:mtd><mml:maligngroup/><mml:mi>x</mml:mi><mml:malignmark/><mml:mo>=</mml:mo><mml:mn>1</mml:mn></mml:mtd></mml:mtr><mml:mtr><mml:mtd><mml:maligngroup/><mml:mi>y</mml:mi><mml:malignmark/><mml:mo>=</mml:mo><mml:mn>2</mml:mn></mml:mtd></mml:mtr></mml:mtable></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:25:58.000Z" lastModifiedTime="2026-09-13T03:26:00.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{22}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mover><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:mover></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:00.000Z" lastModifiedTime="2026-09-13T03:26:02.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{25}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:munder><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:munder></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:03.000Z" lastModifiedTime="2026-09-13T03:26:04.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mpadded><mml:mi>x</mml:mi></mml:mpadded></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:05.000Z" lastModifiedTime="2026-09-13T03:26:06.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{31}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:menclose notation="box"><mml:mi>x</mml:mi></mml:menclose></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:07.000Z" lastModifiedTime="2026-09-13T03:26:08.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{34}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mo>▱</mml:mo><mml:mfenced><mml:mi>x</mml:mi></mml:mfenced></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:09.000Z" lastModifiedTime="2026-09-13T03:26:10.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{37}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mo>∬</mml:mo><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo><mml:mi>d</mml:mi><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo><mml:mi>d</mml:mi><mml:mi>y</mml:mi><mml:mo>&nbsp;</mml:mo></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:11.000Z" lastModifiedTime="2026-09-13T03:26:12.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>f</mml:mi><mml:mfenced><mml:mi>x</mml:mi></mml:mfenced></mml:mrow><mml:mrow><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:14.000Z" lastModifiedTime="2026-09-13T03:26:14.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{43}{B0}" alignment="left" quickStyleIndex="0" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mover><mml:mo stretchy="false">∑</mml:mo><mml:mi>n</mml:mi></mml:mover><mml:mrow><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-13T03:26:14.000Z" lastModifiedTime="2026-09-13T03:26:14.000Z" objectID="{5C249AD4-AE33-4DED-9D24-FDCE43408E3F}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> |
corpus/math-edit/native-editor-3/read/payloads.json created+3| ... | ... | @@ -0,0 +1,3 @@ |
| 1 | [ | |
| 2 | ||
| 3 | ] | |
| \ No newline at end of file |
corpus/math-edit/native-editor-3/run.json created+18| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | { | |
| 2 | "notebook": "/Users/clo/dev/one/corpus/link-edit/candidate", | |
| 3 | "expected_pages": 1, | |
| 4 | "author": null, | |
| 5 | "author_timeout_seconds": 600, | |
| 6 | "inspect": false, | |
| 7 | "collect_notebook": true, | |
| 8 | "base": { | |
| 9 | "file": "win7-office-base.qcow2", | |
| 10 | "format": "qcow2", | |
| 11 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | |
| 12 | "virtual_size": 68719476736 | |
| 13 | }, | |
| 14 | "scripts": { | |
| 15 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | |
| 16 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | |
| 17 | } | |
| 18 | } |
corpus/math-edit/native-editor-3/scripts/cold.ps1 created+27| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '') | |
| 2 | Set-StrictMode -Version Latest | |
| 3 | $ErrorActionPreference = 'Stop' | |
| 4 | $root = [IO.Path]::GetFullPath($Root).TrimEnd('\') | |
| 5 | if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { | |
| 6 | throw 'Choose a run directly below C:\one-tests\runs.' | |
| 7 | } | |
| 8 | if (Get-Process ONENOTE -ErrorAction SilentlyContinue) { throw 'Close OneNote before resetting its test cache.' } | |
| 9 | $key = 'HKCU:\Software\Microsoft\Office\14.0\OneNote' | |
| 10 | if ($CloneHost) { | |
| 11 | if ($CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or [Environment]::MachineName -ne $CloneHost) { | |
| 12 | throw 'The disposable clone hostname does not match this machine.' | |
| 13 | } | |
| 14 | New-Item "$key\Options\Paths" -Force | Out-Null | |
| 15 | New-ItemProperty "$key\Options\Paths" -Name UnfiledNotesSection -PropertyType ExpandString -Value 'C:\one-tests\Loose.one' -Force | Out-Null | |
| 16 | } elseif ((Get-ItemProperty "$key\Options\Paths").UnfiledNotesSection -ne 'C:\one-tests\Loose.one' -or | |
| 17 | -not (Test-Path 'C:\one-tests\profile-original-cache')) { | |
| 18 | throw 'Park the personal OneNote profile before resetting the test cache.' | |
| 19 | } | |
| 20 | $cache = Join-Path $env:LOCALAPPDATA 'Microsoft\OneNote\14.0' | |
| 21 | $parked = Join-Path 'C:\one-tests\caches' ([IO.Path]::GetFileName($root)) | |
| 22 | if (Test-Path $parked) { throw 'Choose a new run; its parked cache already exists.' } | |
| 23 | New-Item -ItemType Directory -Path 'C:\one-tests\caches' -Force | Out-Null | |
| 24 | if (Test-Path $cache) { Move-Item -LiteralPath $cache -Destination $parked } | |
| 25 | if (Test-Path "$key\OpenNotebooks") { Remove-Item "$key\OpenNotebooks" -Recurse } | |
| 26 | New-Item "$key\OpenNotebooks" | Out-Null | |
| 27 | New-ItemProperty "$key\OpenNotebooks" -Name '1' -PropertyType String -Value "$root\notebook" | Out-Null |
corpus/math-edit/native-editor-3/scripts/read.ps1 created+142| ... | ... | @@ -0,0 +1,142 @@ |
| 1 | param( | |
| 2 | [Parameter(Mandatory=$true)][string]$Root, | |
| 3 | [int]$ExpectedPages = -1, | |
| 4 | [switch]$UseCurrentCache, | |
| 5 | [switch]$Pdf, | |
| 6 | [switch]$KeepOpen, | |
| 7 | [string]$CloneHost = '' | |
| 8 | ) | |
| 9 | Set-StrictMode -Version Latest | |
| 10 | $ErrorActionPreference = 'Stop' | |
| 11 | $root = [IO.Path]::GetFullPath($Root).TrimEnd('\') | |
| 12 | if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') { | |
| 13 | throw 'Choose a run directly below C:\one-tests\runs.' | |
| 14 | } | |
| 15 | $notebook = Join-Path $root 'notebook' | |
| 16 | $output = Join-Path $root 'read' | |
| 17 | if (Test-Path $output) { throw 'Choose a new read destination.' } | |
| 18 | if ($UseCurrentCache) { | |
| 19 | if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') { | |
| 20 | throw 'Park the personal OneNote profile before reading test notebooks.' | |
| 21 | } | |
| 22 | } else { | |
| 23 | & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost | |
| 24 | } | |
| 25 | New-Item -ItemType Directory -Path $output | Out-Null | |
| 26 | $app = New-Object -ComObject OneNote.Application | |
| 27 | $notebookId = '' | |
| 28 | $failure = $null | |
| 29 | try { | |
| 30 | $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0) | |
| 31 | $process = Get-Process ONENOTE | |
| 32 | @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion; | |
| 33 | powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } | | |
| 34 | ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8 | |
| 35 | $sections = @() | |
| 36 | foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) { | |
| 37 | $id = '' | |
| 38 | $app.OpenHierarchy($file.FullName, '', [ref]$id, 0) | |
| 39 | $sections += $id | |
| 40 | } | |
| 41 | $deadline = [DateTime]::UtcNow.AddSeconds(300) | |
| 42 | $previous = '' | |
| 43 | $lastChange = '' | |
| 44 | $stableSince = [DateTime]::UtcNow | |
| 45 | $settled = $false | |
| 46 | do { | |
| 47 | $pages = @{} | |
| 48 | foreach ($section in $sections) { | |
| 49 | $hierarchy = '' | |
| 50 | $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) | |
| 51 | [xml]$xml = $hierarchy | |
| 52 | foreach ($node in $xml.SelectNodes('//*[@path]')) { | |
| 53 | if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) { | |
| 54 | throw 'OneNote opened a section outside the copied notebook.' | |
| 55 | } | |
| 56 | } | |
| 57 | foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) { | |
| 58 | $id = $node.GetAttribute('ID') | |
| 59 | $content = '' | |
| 60 | $app.GetPageContent($id, [ref]$content, 1, 1) | |
| 61 | $pages[$id] = $content | |
| 62 | } | |
| 63 | } | |
| 64 | $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] })) | |
| 65 | if ($signature -ne $previous) { | |
| 66 | $lastChange = $previous | |
| 67 | $previous = $signature | |
| 68 | $stableSince = [DateTime]::UtcNow | |
| 69 | } | |
| 70 | if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or | |
| 71 | ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and | |
| 72 | ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break } | |
| 73 | Start-Sleep -Milliseconds 250 | |
| 74 | } while ([DateTime]::UtcNow -lt $deadline) | |
| 75 | if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) { | |
| 76 | [IO.File]::WriteAllText((Join-Path $output 'previous-signature.txt'), $lastChange, [Text.Encoding]::UTF8) | |
| 77 | $index = 0 | |
| 78 | foreach ($id in @($pages.Keys | Sort-Object)) { | |
| 79 | [IO.File]::WriteAllText((Join-Path $output ('unsettled-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) | |
| 80 | $index++ | |
| 81 | } | |
| 82 | $hierarchy = '' | |
| 83 | $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1) | |
| 84 | [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8) | |
| 85 | throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled." | |
| 86 | } | |
| 87 | $index = 0 | |
| 88 | $payloads = @() | |
| 89 | foreach ($id in @($pages.Keys | Sort-Object)) { | |
| 90 | [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8) | |
| 91 | if ($Pdf) { | |
| 92 | $pdfPath = Join-Path $output ('page-{0:d3}.pdf' -f $index) | |
| 93 | $app.NavigateTo($id, '', $false) | |
| 94 | $app.Publish($id, $pdfPath, 3, '') | |
| 95 | if (-not (Test-Path $pdfPath) -or (Get-Item $pdfPath).Length -eq 0) { throw 'OneNote did not publish the page PDF.' } | |
| 96 | } | |
| 97 | [xml]$page = $pages[$id] | |
| 98 | foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile" or local-name()="MediaFile"]')) { | |
| 99 | $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache')) | |
| 100 | $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant() | |
| 101 | [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes) | |
| 102 | $payloads += @{ page = $id; object = $file.ParentNode.GetAttribute('objectID'); | |
| 103 | kind = $file.LocalName; name = $file.GetAttribute('preferredName'); | |
| 104 | sha256 = $hash; bytes = $bytes.Length } | |
| 105 | } | |
| 106 | $index++ | |
| 107 | } | |
| 108 | [IO.File]::WriteAllText((Join-Path $output 'payloads.json'), (ConvertTo-Json -InputObject $payloads -Depth 4), [Text.Encoding]::UTF8) | |
| 109 | $all = '' | |
| 110 | $app.GetHierarchy($notebookId, 4, [ref]$all, 1) | |
| 111 | [xml]$finalTree = $all | |
| 112 | $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique) | |
| 113 | if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) { | |
| 114 | throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.' | |
| 115 | } | |
| 116 | [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8) | |
| 117 | Write-Output "Read $($sections.Count) sections and $($pages.Count) pages." | |
| 118 | } catch { | |
| 119 | $failure = $_ | |
| 120 | [IO.File]::WriteAllText((Join-Path $output 'failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) | |
| 121 | throw | |
| 122 | } finally { | |
| 123 | try { | |
| 124 | try { | |
| 125 | if ($notebookId -and $CloneHost) { $app.SyncHierarchy($notebookId) } | |
| 126 | if ($notebookId -and -not $KeepOpen -and (-not $UseCurrentCache -or $CloneHost)) { | |
| 127 | $app.CloseNotebook($notebookId, $false) | |
| 128 | } | |
| 129 | } catch { | |
| 130 | if ($null -eq $failure) { throw } | |
| 131 | [IO.File]::WriteAllText((Join-Path $output 'cleanup-failure.txt'), ($_ | Out-String), [Text.Encoding]::UTF8) | |
| 132 | } | |
| 133 | } finally { | |
| 134 | [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) | |
| 135 | } | |
| 136 | $app = $null | |
| 137 | [GC]::Collect() | |
| 138 | [GC]::WaitForPendingFinalizers() | |
| 139 | if (-not $UseCurrentCache -and -not $CloneHost) { | |
| 140 | Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10 | |
| 141 | } | |
| 142 | } |
corpus/math-edit/native-editor-3/source.json created+14| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | [ | |
| 2 | { | |
| 3 | "path": "Open Notebook.onetoc2", | |
| 4 | "bytes": 1604, | |
| 5 | "sha256": "6731c4214437fe1cb6aa347b0004542b7f7e4260f47773bf9ec07c4e894de47c", | |
| 6 | "mtime_ns": 1789206821144296891 | |
| 7 | }, | |
| 8 | { | |
| 9 | "path": "links.one", | |
| 10 | "bytes": 6128, | |
| 11 | "sha256": "0cfb356ed374fe919f73fb73fdbe5d66e0baa484d4119f70074d9f869d6db78a", | |
| 12 | "mtime_ns": 1789206821116200533 | |
| 13 | } | |
| 14 | ] |
corpus/math-edit/native-editor-3/teardown.json created+1| ... | ... | @@ -0,0 +1 @@ |
| 1 | {"absent": true} |
crates/onestore/src/document.rs+3| ... | ... | @@ -136,6 +136,8 @@ format_fields! { |
| 136 | 136 | pub struct MathObject { |
| 137 | 137 | pub kind: u32, |
| 138 | 138 | pub arguments: Option<u32>, |
| 139 | /// Column count of a matrix or equation array. | |
| 140 | pub columns: Option<u8>, | |
| 139 | 141 | pub symbols: Vec<char>, |
| 140 | 142 | } |
| 141 | 143 | |
| ... | ... | @@ -232,6 +234,7 @@ impl Revision<'_> { |
| 232 | 234 | }) |
| 233 | 235 | }) |
| 234 | 236 | .transpose()?, |
| 237 | columns: scalar(0x0c003451).and_then(|b| b.first().copied()), | |
| 235 | 238 | symbols: [0x10003453, 0x10003454, 0x10003455] |
| 236 | 239 | .into_iter() |
| 237 | 240 | .filter_map(scalar) |
crates/onestore/src/page/math.rs+69-10| ... | ... | @@ -1,9 +1,9 @@ |
| 1 | 1 | //! Equations as OneNote stores them: a linear text where U+FDD0 opens an inline object, |
| 2 | 2 | //! U+FDEE separates its arguments and U+FDEF closes it, with the object's kind on the run |
| 3 | 3 | //! data of the opening character. `Math::parse` builds the tree and `mathml` renders it the |
| 4 | //! way OneNote's own export does for the kinds native fixtures have verified (superscript, | |
| 5 | //! subscript, both, fraction, brackets, n-ary operators with limits); radicals, under/over | |
| 6 | //! limits and the rest follow the text object model's meaning without a native check. | |
| 4 | //! way OneNote's own export does, verified against native fixtures for every kind the | |
| 5 | //! equation editor produced: scripts, fractions, fences, n-ary operators with any limits, | |
| 6 | //! radicals, limit objects, accents, overbars, boxes, matrices and equation arrays. | |
| 7 | 7 | |
| 8 | 8 | use super::text::Paragraph; |
| 9 | 9 | use crate::document::MathObject; |
| ... | ... | @@ -18,6 +18,9 @@ pub enum Math { |
| 18 | 18 | Object { |
| 19 | 19 | kind: u32, |
| 20 | 20 | symbols: Vec<char>, |
| 21 | /// Column count of a matrix (arguments are its cells, row by row) or an equation | |
| 22 | /// array (arguments are its rows). | |
| 23 | columns: Option<u8>, | |
| 21 | 24 | arguments: Vec<Vec<Math>>, |
| 22 | 25 | }, |
| 23 | 26 | } |
| ... | ... | @@ -48,6 +51,7 @@ impl Math { |
| 48 | 51 | struct Frame { |
| 49 | 52 | kind: u32, |
| 50 | 53 | symbols: Vec<char>, |
| 54 | columns: Option<u8>, | |
| 51 | 55 | arguments: Vec<Vec<Math>>, |
| 52 | 56 | } |
| 53 | 57 | let mut frames: Vec<Frame> = Vec::new(); |
| ... | ... | @@ -83,6 +87,7 @@ impl Math { |
| 83 | 87 | frames.push(Frame { |
| 84 | 88 | kind: object.kind, |
| 85 | 89 | symbols: object.symbols.clone(), |
| 90 | columns: object.columns, | |
| 86 | 91 | arguments: Vec::new(), |
| 87 | 92 | }); |
| 88 | 93 | sequences.push(Vec::new()); |
| ... | ... | @@ -105,6 +110,7 @@ impl Math { |
| 105 | 110 | sequences.last_mut().unwrap().push(Math::Object { |
| 106 | 111 | kind: frame.kind, |
| 107 | 112 | symbols: frame.symbols, |
| 113 | columns: frame.columns, | |
| 108 | 114 | arguments: frame.arguments, |
| 109 | 115 | }); |
| 110 | 116 | } |
| ... | ... | @@ -147,6 +153,7 @@ impl Math { |
| 147 | 153 | Math::Object { |
| 148 | 154 | kind, |
| 149 | 155 | symbols, |
| 156 | columns, | |
| 150 | 157 | arguments, |
| 151 | 158 | } => { |
| 152 | 159 | // A lone letter, number or operator stands bare; anything else is a row. |
| ... | ... | @@ -176,6 +183,44 @@ impl Math { |
| 176 | 183 | out.push_str(&format!("</mml:{element}>")); |
| 177 | 184 | }; |
| 178 | 185 | match (kind, arguments.len()) { |
| 186 | (11, 1) => wrapped(out, "mpadded", &[0]), | |
| 187 | (12, 1) => { | |
| 188 | out.push_str("<mml:menclose notation=\"box\">"); | |
| 189 | argument(out, 0); | |
| 190 | out.push_str("</mml:menclose>"); | |
| 191 | } | |
| 192 | // Matrix cells row by row; an equation array's rows carry `&` alignment | |
| 193 | // marks, exported as an alignment group at the row start and a mark at each. | |
| 194 | (20, _) | (15, _) => { | |
| 195 | let width = if *kind == 20 { | |
| 196 | usize::from(columns.unwrap_or(1).max(1)) | |
| 197 | } else { | |
| 198 | 1 | |
| 199 | }; | |
| 200 | out.push_str("<mml:mtable>"); | |
| 201 | for (r, row) in arguments.chunks(width).enumerate() { | |
| 202 | out.push_str("<mml:mtr>"); | |
| 203 | for (c, cell) in row.iter().enumerate() { | |
| 204 | out.push_str("<mml:mtd>"); | |
| 205 | if *kind == 15 { | |
| 206 | out.push_str("<mml:maligngroup/>"); | |
| 207 | for node in cell { | |
| 208 | match node { | |
| 209 | Math::Operator('&') => { | |
| 210 | out.push_str("<mml:malignmark/>") | |
| 211 | } | |
| 212 | node => node.write(out), | |
| 213 | } | |
| 214 | } | |
| 215 | } else { | |
| 216 | argument(out, r * width + c); | |
| 217 | } | |
| 218 | out.push_str("</mml:mtd>"); | |
| 219 | } | |
| 220 | out.push_str("</mml:mtr>"); | |
| 221 | } | |
| 222 | out.push_str("</mml:mtable>"); | |
| 223 | } | |
| 179 | 224 | (31, 2) => wrapped(out, "msup", &[0, 1]), |
| 180 | 225 | (29, 2) => wrapped(out, "msub", &[0, 1]), |
| 181 | 226 | (30, 3) => wrapped(out, "msubsup", &[0, 1, 2]), |
| ... | ... | @@ -220,19 +265,29 @@ impl Math { |
| 220 | 265 | out.push_str("</mml:mover>"); |
| 221 | 266 | } |
| 222 | 267 | // Lower limit, upper limit, body; integrals take their limits as scripts, |
| 223 | // other operators above and below. | |
| 268 | // other operators above and below, and an empty limit leaves its side out. | |
| 224 | 269 | (21, 3) => { |
| 225 | 270 | let operator = symbols.first().copied().unwrap_or('∑'); |
| 226 | let element = if ('\u{222b}'..='\u{2233}').contains(&operator) { | |
| 227 | "msubsup" | |
| 228 | } else { | |
| 229 | "munderover" | |
| 271 | let scripts = ('\u{222b}'..='\u{2233}').contains(&operator); | |
| 272 | let (lower, upper) = (!arguments[0].is_empty(), !arguments[1].is_empty()); | |
| 273 | let element = match (lower, upper, scripts) { | |
| 274 | (true, true, true) => "msubsup", | |
| 275 | (true, true, false) => "munderover", | |
| 276 | (true, false, true) => "msub", | |
| 277 | (true, false, false) => "munder", | |
| 278 | (false, true, true) => "msup", | |
| 279 | (false, true, false) => "mover", | |
| 280 | (false, false, _) => "mrow", | |
| 230 | 281 | }; |
| 231 | 282 | out.push_str(&format!( |
| 232 | 283 | "<mml:{element}><mml:mo stretchy=\"false\">{operator}</mml:mo>" |
| 233 | 284 | )); |
| 234 | argument(out, 0); | |
| 235 | argument(out, 1); | |
| 285 | if lower { | |
| 286 | argument(out, 0); | |
| 287 | } | |
| 288 | if upper { | |
| 289 | argument(out, 1); | |
| 290 | } | |
| 236 | 291 | out.push_str(&format!("</mml:{element}>")); |
| 237 | 292 | out.push_str("<mml:mrow>"); |
| 238 | 293 | for node in &arguments[2] { |
| ... | ... | @@ -320,6 +375,7 @@ impl Math { |
| 320 | 375 | format.math_object = Some(object.unwrap_or(MathObject { |
| 321 | 376 | kind: PLAIN_RUN, |
| 322 | 377 | arguments: None, |
| 378 | columns: None, | |
| 323 | 379 | symbols: Vec::new(), |
| 324 | 380 | })); |
| 325 | 381 | (text, format) |
| ... | ... | @@ -341,6 +397,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option<MathObject>)>) |
| 341 | 397 | Math::Object { |
| 342 | 398 | kind, |
| 343 | 399 | symbols, |
| 400 | columns, | |
| 344 | 401 | arguments, |
| 345 | 402 | } => { |
| 346 | 403 | if !leaf.is_empty() { |
| ... | ... | @@ -351,6 +408,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option<MathObject>)>) |
| 351 | 408 | Some(MathObject { |
| 352 | 409 | kind: *kind, |
| 353 | 410 | arguments: Some(arguments.len() as u32), |
| 411 | columns: *columns, | |
| 354 | 412 | symbols: symbols.clone(), |
| 355 | 413 | }), |
| 356 | 414 | )); |
| ... | ... | @@ -365,6 +423,7 @@ fn write_sequence(nodes: &[Math], runs: &mut Vec<(String, Option<MathObject>)>) |
| 365 | 423 | let object = MathObject { |
| 366 | 424 | kind: *kind, |
| 367 | 425 | arguments: (index > 0).then_some(index as u32), |
| 426 | columns: None, | |
| 368 | 427 | symbols: Vec::new(), |
| 369 | 428 | }; |
| 370 | 429 | let extended = runs.len() > before; |
crates/onestore/src/page/write.rs+3| ... | ... | @@ -1770,6 +1770,9 @@ impl Lowering<'_> { |
| 1770 | 1770 | if let Some(count) = object.arguments { |
| 1771 | 1771 | set.push((0x14003450, count.to_le_bytes().to_vec())); |
| 1772 | 1772 | } |
| 1773 | if let Some(columns) = object.columns { | |
| 1774 | set.push((0x0c003451, vec![columns])); | |
| 1775 | } | |
| 1773 | 1776 | for (id, symbol) in [0x10003453, 0x10003454, 0x10003455] |
| 1774 | 1777 | .into_iter() |
| 1775 | 1778 | .zip(&object.symbols) |
crates/onestore/tests/page_math.rs+11| ... | ... | @@ -18,6 +18,12 @@ const EDITOR_2: &[u8] = |
| 18 | 18 | include_bytes!("../../../corpus/math-edit/native-editor-2/notebook/links.one"); |
| 19 | 19 | const EDITOR_2_READ: &str = |
| 20 | 20 | include_str!("../../../corpus/math-edit/native-editor-2/read/page-000.xml"); |
| 21 | /// A third session: a matrix, an equation array, upper and lower limit objects, boxes, a | |
| 22 | /// nested fraction and a sum with only an upper limit. | |
| 23 | const EDITOR_3: &[u8] = | |
| 24 | include_bytes!("../../../corpus/math-edit/native-editor-3/notebook/links.one"); | |
| 25 | const EDITOR_3_READ: &str = | |
| 26 | include_str!("../../../corpus/math-edit/native-editor-3/read/page-000.xml"); | |
| 21 | 27 | |
| 22 | 28 | fn native_mathml(read: &str) -> Vec<String> { |
| 23 | 29 | read.split("<mml:math") |
| ... | ... | @@ -101,12 +107,17 @@ fn equation_editor_expressions_render_to_the_mathml_onenote_exports() { |
| 101 | 107 | rendered_equations(EDITOR_2, "Read about Rust the Rust site"), |
| 102 | 108 | native_mathml(EDITOR_2_READ) |
| 103 | 109 | ); |
| 110 | assert_eq!( | |
| 111 | rendered_equations(EDITOR_3, "Read about Rust the Rust site"), | |
| 112 | native_mathml(EDITOR_3_READ) | |
| 113 | ); | |
| 104 | 114 | } |
| 105 | 115 | |
| 106 | 116 | fn object(kind: u32, symbols: &str, arguments: Vec<Vec<Math>>) -> Math { |
| 107 | 117 | Math::Object { |
| 108 | 118 | kind, |
| 109 | 119 | symbols: symbols.chars().collect(), |
| 120 | columns: None, | |
| 110 | 121 | arguments, |
| 111 | 122 | } |
| 112 | 123 | } |
tools/test_math_edit.py+1| ... | ... | @@ -23,6 +23,7 @@ class MathEditTest(unittest.TestCase): |
| 23 | 23 | def test_equation_editor_expressions_export_as_mathml(self): |
| 24 | 24 | self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor/read/page-000.xml')), 5) |
| 25 | 25 | self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor-2/read/page-000.xml')), 9) |
| 26 | self.assertEqual(len(exported_mathml(FIXTURE / 'native-editor-3/read/page-000.xml')), 10) | |
| 26 | 27 | |
| 27 | 28 | def test_rust_written_equations_export_the_expected_mathml(self): |
| 28 | 29 | with TemporaryDirectory() as temporary: |