diff --git a/corpus/formatted-insertion/README.md b/corpus/formatted-insertion/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ad8b82021cc3c21a2f2bf8fecbbd027073cfa1ff --- /dev/null +++ b/corpus/formatted-insertion/README.md @@ -0,0 +1,31 @@ +# Atomic formatted insertions + +Synthetic Rust paragraph/outline insertions with independent OneNote 2010 cold +reads. The native paragraph and table inputs descend from the public +`native/20260905-05` corpus. `manifest.json` records stable intents and file hashes; +each capture retains its native environment and run provenance. Attachment exports +link to existing identical public payloads. + +Generate new candidates with the ignored `export_native_formatted_insertions` test +and `ONESTORE_INSERT_OUTPUT` set to a new absolute directory. Each child directory +is a separate notebook: the candidates can share source section identities. +Use `tools/native_runner.py` with one child directory and `--expected-pages 1`. +`tools/native/formatted-insertion.ps1` performs an unchanged native HTML round trip +followed by a native prefix edit for the four nonempty cases. + +The unchanged round trip establishes native font substitution before checking the +edit's exact character styles. Table updates omit the outline's measured `Size`: +resubmitting it made two unlocked columns oscillate in native XML, including after +a fresh cache reopen. Those unsettled captures are excluded. The retained table +reverse capture omits that measurement; paragraph and ordinary outline captures +retain it. + +`native-cell/ui` records a direct keyboard prefix edit, without HTML resubmission. +Its native XML settles, preserves every prior character's style, and the resulting +file passes the same model oracle. The capture includes the keyboard script and +screenshot. + +COM omits the empty outline. Its cold-reopened file retains the empty rich-text +object and style; `empty/typed-after-cold` adds text to that retained object and +verifies the 18-point italic result in another independent native cold read. +`tools/test_formatted_insertion.py` checks these captures without a live VM. diff --git a/corpus/formatted-insertion/empty-typed/candidate/synthetic.one b/corpus/formatted-insertion/empty-typed/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..5b2a46d40debafcf9c499c4140447db1f4ccbe1e Binary files /dev/null and b/corpus/formatted-insertion/empty-typed/candidate/synthetic.one differ diff --git a/corpus/formatted-insertion/empty-typed/cold/notebook/synthetic.one b/corpus/formatted-insertion/empty-typed/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..7aff330865bcd3f0594d7a3675fc0a612abae114 Binary files /dev/null and b/corpus/formatted-insertion/empty-typed/cold/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/empty-typed/cold/read/environment.json b/corpus/formatted-insertion/empty-typed/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..81c0a7c8ec62f9c53f6146ec8133408ea8c7c45d --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-7135C1A2", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/empty-typed/cold/read/hierarchy.xml b/corpus/formatted-insertion/empty-typed/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d2fa43e89d3a0bdc75007f82c85d83e5a91b07a --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/empty-typed/cold/read/page-000.xml b/corpus/formatted-insertion/empty-typed/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..d97ce299694f9a94759e3a7ac41b3ba826777024 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/read/page-000.xml @@ -0,0 +1,3 @@ + +Typed café 🦀]]> diff --git a/corpus/formatted-insertion/empty-typed/cold/read/payloads.json b/corpus/formatted-insertion/empty-typed/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/empty-typed/cold/run.json b/corpus/formatted-insertion/empty-typed/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..74732718857f2351183efd113137f242db48ea00 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/empty-typed", + "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": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/empty-typed/cold/source.json b/corpus/formatted-insertion/empty-typed/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..b1e1853877c2d36db5c3289b86965dc8e1c8bf4f --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 6016, + "sha256": "855460055414c52034d0cf62b2e855b460deaedc17d1f276d6b3738458608f2e", + "mtime_ns": 1788842844349962032 + } +] diff --git a/corpus/formatted-insertion/empty-typed/cold/teardown.json b/corpus/formatted-insertion/empty-typed/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/empty-typed/reverse/control-before.xml b/corpus/formatted-insertion/empty-typed/reverse/control-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..29b567bed2e6bcad406ce6e70b1323b6ea20c9dd --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/control-before.xml @@ -0,0 +1,3 @@ + +Typed café 🦀]]> diff --git a/corpus/formatted-insertion/empty-typed/reverse/control-update.xml b/corpus/formatted-insertion/empty-typed/reverse/control-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..044be53b9e918217202755dd1314cf85743cd494 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/control-update.xml @@ -0,0 +1,2 @@ +Typed café 🦀]]> \ No newline at end of file diff --git a/corpus/formatted-insertion/empty-typed/reverse/edit-before.xml b/corpus/formatted-insertion/empty-typed/reverse/edit-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..045b78c7927a7138aa963a08fdf41f77dfcc60ff --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/edit-before.xml @@ -0,0 +1,3 @@ + +Typed café 🦀]]> diff --git a/corpus/formatted-insertion/empty-typed/reverse/edit-update.xml b/corpus/formatted-insertion/empty-typed/reverse/edit-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..1697afd71d7da49fbcd16e18203a356535d50fca --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/edit-update.xml @@ -0,0 +1,2 @@ +<span +style='font-style:italic'>Native Typed café &#129408;</span> \ No newline at end of file diff --git a/corpus/formatted-insertion/empty-typed/reverse/notebook/synthetic.one b/corpus/formatted-insertion/empty-typed/reverse/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..c1da8e5d8a2bb4943419e48fee7edfb0ae7d827f Binary files /dev/null and b/corpus/formatted-insertion/empty-typed/reverse/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/empty-typed/reverse/read/environment.json b/corpus/formatted-insertion/empty-typed/reverse/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..473de67a845048773370f7b6515648ff7f7c421a --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-8F1E54CA", + "cold": false, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/empty-typed/reverse/read/hierarchy.xml b/corpus/formatted-insertion/empty-typed/reverse/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..293fac7d3ee06bd76d46863e11035ba45254a724 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/empty-typed/reverse/read/page-000.xml b/corpus/formatted-insertion/empty-typed/reverse/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..764acda1d3451afd2a0ba4f724574669785cbcda --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/read/page-000.xml @@ -0,0 +1,3 @@ + +Native Typed café 🦀]]> diff --git a/corpus/formatted-insertion/empty-typed/reverse/read/payloads.json b/corpus/formatted-insertion/empty-typed/reverse/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/empty-typed/reverse/reverse-verification.json b/corpus/formatted-insertion/empty-typed/reverse/reverse-verification.json new file mode 100644 index 0000000000000000000000000000000000000000..c70ef0aaa3d5e0377597d673edefc1198517e912 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/reverse-verification.json @@ -0,0 +1,5 @@ +{ + "paragraphs": 2, + "characters": 27, + "exact_native_character_styles_after_control": true +} diff --git a/corpus/formatted-insertion/empty-typed/reverse/run.json b/corpus/formatted-insertion/empty-typed/reverse/run.json new file mode 100644 index 0000000000000000000000000000000000000000..810bee75a113c5051eded466b57f95f61496df3e --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/run.json @@ -0,0 +1,19 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/empty-typed", + "expected_pages": 1, + "author": "evidence/m10/formatted-reverse-controlled.ps1", + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "author.ps1": "4362d0ba551eb00b2eca1c5563d76352469f85d3981155bf2682c159a03ddedb", + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "df4244a18ab3e9c21083b8df61b08b863e8cce0ec4bf0c9cd742a8ee6f79877d" + } +} diff --git a/corpus/formatted-insertion/empty-typed/reverse/source.json b/corpus/formatted-insertion/empty-typed/reverse/source.json new file mode 100644 index 0000000000000000000000000000000000000000..b1e1853877c2d36db5c3289b86965dc8e1c8bf4f --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 6016, + "sha256": "855460055414c52034d0cf62b2e855b460deaedc17d1f276d6b3738458608f2e", + "mtime_ns": 1788842844349962032 + } +] diff --git a/corpus/formatted-insertion/empty-typed/reverse/teardown.json b/corpus/formatted-insertion/empty-typed/reverse/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/empty-typed/reverse/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/empty/candidate/synthetic.one b/corpus/formatted-insertion/empty/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..2ba1e5a1323d980d05cf2a75fbe1c0e42761d60f Binary files /dev/null and b/corpus/formatted-insertion/empty/candidate/synthetic.one differ diff --git a/corpus/formatted-insertion/empty/cold/notebook/synthetic.one b/corpus/formatted-insertion/empty/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..6d33cf8d437c8dc0fe0941484190e6e1204f2f03 Binary files /dev/null and b/corpus/formatted-insertion/empty/cold/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/empty/cold/read/environment.json b/corpus/formatted-insertion/empty/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..14c6828d1b0879e791c631ba60831a3def0ea43c --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-6DB25082", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/empty/cold/read/hierarchy.xml b/corpus/formatted-insertion/empty/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..46ecee1aff9e3f3c46a16a9388693c6427935ad5 --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/empty/cold/read/page-000.xml b/corpus/formatted-insertion/empty/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..ff3c9ab7a5343a439d3b7c95b70001d7401565b4 --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/read/page-000.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/empty/cold/read/payloads.json b/corpus/formatted-insertion/empty/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/empty/cold/run.json b/corpus/formatted-insertion/empty/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..474dda4dfb0fc5c51a5f4f067812ec9dce238d67 --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/empty", + "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": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/empty/cold/source.json b/corpus/formatted-insertion/empty/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..771e0026ba3fd91a3827e6123b56fdb304dd7101 --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 5624, + "sha256": "bd592be42dc93e95444174ceceb19d1fc718fbe53c08403f87b6dec3207dc38d", + "mtime_ns": 1788842844347041269 + } +] diff --git a/corpus/formatted-insertion/empty/cold/teardown.json b/corpus/formatted-insertion/empty/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/empty/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/empty/typed-after-cold/candidate/synthetic.one b/corpus/formatted-insertion/empty/typed-after-cold/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..17346445e96fd59d30a9d7bd5253140ff426a784 Binary files /dev/null and b/corpus/formatted-insertion/empty/typed-after-cold/candidate/synthetic.one differ diff --git a/corpus/formatted-insertion/empty/typed-after-cold/read/environment.json b/corpus/formatted-insertion/empty/typed-after-cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..42ddb9f651edfa0c158724e69628cf9b27f98cc3 --- /dev/null +++ b/corpus/formatted-insertion/empty/typed-after-cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-2A09ABA3", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/empty/typed-after-cold/read/hierarchy.xml b/corpus/formatted-insertion/empty/typed-after-cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..1b169347a442e3e5673a58c50a2c89acff3a6745 --- /dev/null +++ b/corpus/formatted-insertion/empty/typed-after-cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/empty/typed-after-cold/read/page-000.xml b/corpus/formatted-insertion/empty/typed-after-cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..ca9eafad77a31fb7619e7a180645929b95c4abd9 --- /dev/null +++ b/corpus/formatted-insertion/empty/typed-after-cold/read/page-000.xml @@ -0,0 +1,3 @@ + +Typed after native reopen 🦀]]> diff --git a/corpus/formatted-insertion/empty/typed-after-cold/read/payloads.json b/corpus/formatted-insertion/empty/typed-after-cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/empty/typed-after-cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/manifest.json b/corpus/formatted-insertion/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..f1b605d73d029a93e72488a66a3871b541d277d3 --- /dev/null +++ b/corpus/formatted-insertion/manifest.json @@ -0,0 +1,963 @@ +{ + "generator": "crates/onestore/tests/insertion.rs:export_native_formatted_insertions", + "cases": [ + { + "intent": { + "author": "Rich author", + "created": 1473310044, + "formats": [ + { + "attributes": [ + { + "Bold": true + }, + { + "Underline": true + } + ], + "guid": [ + 207, + 169, + 223, + 216, + 230, + 235, + 235, + 75, + 185, + 94, + 69, + 228, + 117, + 205, + 151, + 134 + ], + "range": { + "end": 4, + "start": 0 + } + }, + { + "attributes": [ + { + "Font": "Arial" + }, + { + "FontSize": 13.5 + } + ], + "guid": [ + 233, + 34, + 183, + 44, + 7, + 99, + 201, + 75, + 140, + 220, + 83, + 226, + 209, + 176, + 83, + 88 + ], + "range": { + "end": 7, + "start": 5 + } + }, + { + "attributes": [ + { + "Italic": true + }, + { + "Strike": true + } + ], + "guid": [ + 30, + 196, + 121, + 136, + 27, + 75, + 93, + 79, + 181, + 130, + 25, + 17, + 13, + 139, + 35, + 217 + ], + "range": { + "end": 14, + "start": 8 + } + }, + { + "attributes": [ + { + "Superscript": true + } + ], + "guid": [ + 101, + 236, + 26, + 203, + 134, + 120, + 215, + 67, + 170, + 155, + 81, + 250, + 86, + 214, + 214, + 111 + ], + "range": { + "end": 17, + "start": 15 + } + }, + { + "attributes": [ + { + "Color": [ + 12, + 34, + 56 + ] + }, + { + "Highlight": [ + 255, + 255, + 0 + ] + } + ], + "guid": [ + 57, + 2, + 72, + 237, + 92, + 155, + 100, + 72, + 130, + 100, + 120, + 181, + 179, + 69, + 97, + 11 + ], + "range": { + "end": 23, + "start": 18 + } + }, + { + "attributes": [ + { + "Subscript": true + } + ], + "guid": [ + 189, + 73, + 14, + 147, + 222, + 140, + 144, + 65, + 176, + 86, + 229, + 249, + 220, + 119, + 252, + 192 + ], + "range": { + "end": 26, + "start": 24 + } + }, + { + "attributes": [ + { + "Bold": false + }, + { + "Italic": false + }, + { + "Color": null + }, + { + "Highlight": null + } + ], + "guid": [ + 165, + 80, + 86, + 53, + 207, + 121, + 164, + 68, + 169, + 152, + 106, + 34, + 115, + 110, + 230, + 151 + ], + "range": { + "end": 30, + "start": 27 + } + } + ], + "guid": [ + 205, + 161, + 237, + 192, + 33, + 131, + 161, + 64, + 168, + 94, + 233, + 150, + 178, + 37, + 36, + 190 + ], + "parent": "{71AF36A5-6150-452F-B7F3-66BFFF4C3582},22", + "placement": { + "Outline": { + "x": 72.0, + "y": 144.0 + } + }, + "text": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac\rEnd" + }, + "name": "outline", + "space": "{A8C9EFCE-DF4E-462A-866B-6AD8DFFE4C30},1", + "candidate_sha256": "76f6b743caa9dc2b5ab342d6e6a742125ecbcd0ca2372062b42acbd08b5ebb4c", + "cold_sha256": "151a49d8cf2e607a10a9c948286ce7eb3eb754fa90e9557a74a2cb1f1f0204cb", + "reverse_sha256": "8127b63d52ecbc2ad88a0d4f0eccb7fc8b086983f25db40b5f3396616f39cc53", + "omitted_table_outline_size": false + }, + { + "intent": { + "author": "Rich author", + "created": 1473310044, + "formats": [ + { + "attributes": [ + { + "Bold": true + }, + { + "Underline": true + } + ], + "guid": [ + 141, + 194, + 83, + 170, + 169, + 68, + 33, + 76, + 139, + 83, + 12, + 156, + 72, + 95, + 145, + 140 + ], + "range": { + "end": 4, + "start": 0 + } + }, + { + "attributes": [ + { + "Font": "Arial" + }, + { + "FontSize": 13.5 + } + ], + "guid": [ + 168, + 249, + 0, + 182, + 1, + 200, + 117, + 66, + 179, + 210, + 69, + 6, + 83, + 62, + 157, + 171 + ], + "range": { + "end": 7, + "start": 5 + } + }, + { + "attributes": [ + { + "Italic": true + }, + { + "Strike": true + } + ], + "guid": [ + 152, + 84, + 172, + 67, + 218, + 72, + 77, + 67, + 143, + 158, + 58, + 16, + 177, + 251, + 222, + 20 + ], + "range": { + "end": 14, + "start": 8 + } + }, + { + "attributes": [ + { + "Superscript": true + } + ], + "guid": [ + 29, + 165, + 47, + 217, + 107, + 120, + 31, + 79, + 148, + 67, + 148, + 232, + 78, + 245, + 157, + 68 + ], + "range": { + "end": 17, + "start": 15 + } + }, + { + "attributes": [ + { + "Color": [ + 12, + 34, + 56 + ] + }, + { + "Highlight": [ + 255, + 255, + 0 + ] + } + ], + "guid": [ + 165, + 189, + 67, + 237, + 2, + 56, + 67, + 70, + 169, + 71, + 239, + 10, + 236, + 192, + 119, + 48 + ], + "range": { + "end": 23, + "start": 18 + } + }, + { + "attributes": [ + { + "Subscript": true + } + ], + "guid": [ + 116, + 118, + 37, + 115, + 74, + 111, + 247, + 70, + 172, + 126, + 100, + 131, + 159, + 151, + 38, + 234 + ], + "range": { + "end": 26, + "start": 24 + } + }, + { + "attributes": [ + { + "Bold": false + }, + { + "Italic": false + }, + { + "Color": null + }, + { + "Highlight": null + } + ], + "guid": [ + 32, + 151, + 42, + 7, + 76, + 55, + 143, + 75, + 178, + 217, + 234, + 71, + 135, + 25, + 75, + 145 + ], + "range": { + "end": 30, + "start": 27 + } + } + ], + "guid": [ + 191, + 62, + 177, + 4, + 17, + 246, + 2, + 68, + 130, + 157, + 199, + 46, + 35, + 34, + 28, + 139 + ], + "parent": "{03D49648-056C-0F08-32C9-0424E8496E7D},10", + "placement": { + "Paragraph": { + "before": "{03D49648-056C-0F08-32C9-0424E8496E7D},11" + } + }, + "text": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac\rEnd" + }, + "name": "native-paragraph", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "202823512283be980b6a139adcf5f4f369d15f17d60ed7b23f9f2289513aa4e9", + "cold_sha256": "efb61aaedbe12cb00434f7e581259d112480158cacb66961d4168081b77a1112", + "reverse_sha256": "753b320528748faa1ae11ee5eb710530d15636361674aab37f4c442ee5b9dd7b", + "omitted_table_outline_size": false + }, + { + "intent": { + "author": "Rich author", + "created": 1473310044, + "formats": [ + { + "attributes": [ + { + "Bold": true + }, + { + "Underline": true + } + ], + "guid": [ + 148, + 53, + 156, + 113, + 44, + 188, + 95, + 71, + 170, + 198, + 38, + 41, + 148, + 201, + 99, + 234 + ], + "range": { + "end": 4, + "start": 0 + } + }, + { + "attributes": [ + { + "Font": "Arial" + }, + { + "FontSize": 13.5 + } + ], + "guid": [ + 17, + 187, + 223, + 32, + 237, + 203, + 222, + 66, + 134, + 24, + 65, + 131, + 32, + 188, + 202, + 177 + ], + "range": { + "end": 7, + "start": 5 + } + }, + { + "attributes": [ + { + "Italic": true + }, + { + "Strike": true + } + ], + "guid": [ + 201, + 233, + 230, + 86, + 91, + 117, + 20, + 68, + 142, + 220, + 224, + 178, + 139, + 52, + 180, + 203 + ], + "range": { + "end": 14, + "start": 8 + } + }, + { + "attributes": [ + { + "Superscript": true + } + ], + "guid": [ + 145, + 203, + 90, + 115, + 34, + 107, + 73, + 66, + 160, + 157, + 25, + 141, + 249, + 128, + 72, + 159 + ], + "range": { + "end": 17, + "start": 15 + } + }, + { + "attributes": [ + { + "Color": [ + 12, + 34, + 56 + ] + }, + { + "Highlight": [ + 255, + 255, + 0 + ] + } + ], + "guid": [ + 53, + 51, + 180, + 64, + 45, + 92, + 153, + 70, + 163, + 88, + 171, + 172, + 200, + 222, + 35, + 19 + ], + "range": { + "end": 23, + "start": 18 + } + }, + { + "attributes": [ + { + "Subscript": true + } + ], + "guid": [ + 249, + 231, + 26, + 88, + 247, + 6, + 107, + 65, + 131, + 6, + 89, + 211, + 188, + 246, + 100, + 155 + ], + "range": { + "end": 26, + "start": 24 + } + }, + { + "attributes": [ + { + "Bold": false + }, + { + "Italic": false + }, + { + "Color": null + }, + { + "Highlight": null + } + ], + "guid": [ + 136, + 160, + 124, + 20, + 185, + 141, + 156, + 75, + 163, + 89, + 177, + 152, + 149, + 251, + 1, + 204 + ], + "range": { + "end": 30, + "start": 27 + } + } + ], + "guid": [ + 245, + 178, + 40, + 105, + 54, + 125, + 175, + 68, + 190, + 59, + 54, + 206, + 186, + 206, + 134, + 87 + ], + "parent": "{C38843E2-F74B-04BD-24AA-A8F3BCAAF6CF},14", + "placement": { + "Paragraph": { + "before": null + } + }, + "text": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac\rEnd" + }, + "name": "native-cell", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "2d01da4a2c8a0cba582d97460b708c77eca38617f9103f4a3cc9eea33f0e0351", + "cold_sha256": "b7324075349e45f31d9e9fb8638691f58efff1989be65f12f7711dc359898a64", + "reverse_sha256": "04134b8db9b52aaf43dc5dfc90464d54802890e5d4966e7163a977e2ebd77e1e", + "omitted_table_outline_size": true, + "ui_sha256": "d67b70016af71db07a8012b85a5016897c08b715c6db8953e1fc45cfe008bc27" + }, + { + "intent": { + "author": "Rich author", + "created": 1473310044, + "formats": [ + { + "attributes": [ + { + "Italic": true + }, + { + "FontSize": 18.0 + } + ], + "guid": [ + 135, + 82, + 13, + 244, + 205, + 187, + 105, + 65, + 153, + 17, + 70, + 241, + 83, + 89, + 99, + 163 + ], + "range": { + "end": 0, + "start": 0 + } + } + ], + "guid": [ + 201, + 196, + 43, + 177, + 4, + 133, + 60, + 73, + 128, + 208, + 16, + 248, + 199, + 36, + 237, + 179 + ], + "parent": "{71AF36A5-6150-452F-B7F3-66BFFF4C3582},22", + "placement": { + "Outline": { + "x": 72.0, + "y": 144.0 + } + }, + "text": "" + }, + "name": "empty", + "space": "{A8C9EFCE-DF4E-462A-866B-6AD8DFFE4C30},1", + "candidate_sha256": "bd592be42dc93e95444174ceceb19d1fc718fbe53c08403f87b6dec3207dc38d", + "cold_sha256": "a0a59d1c5676e2b6742e13791f888c91a3e0494c980b66fb6ef0a3bf324fac7a" + }, + { + "intent": { + "author": "Rich author", + "created": 1473310044, + "formats": [ + { + "attributes": [ + { + "Italic": true + }, + { + "FontSize": 18.0 + } + ], + "guid": [ + 183, + 9, + 145, + 10, + 11, + 69, + 244, + 73, + 144, + 49, + 203, + 207, + 41, + 174, + 161, + 107 + ], + "range": { + "end": 0, + "start": 0 + } + } + ], + "guid": [ + 38, + 199, + 125, + 238, + 88, + 20, + 244, + 67, + 146, + 95, + 200, + 15, + 97, + 115, + 247, + 221 + ], + "parent": "{71AF36A5-6150-452F-B7F3-66BFFF4C3582},22", + "placement": { + "Outline": { + "x": 72.0, + "y": 144.0 + } + }, + "text": "" + }, + "name": "empty-typed", + "space": "{A8C9EFCE-DF4E-462A-866B-6AD8DFFE4C30},1", + "candidate_sha256": "855460055414c52034d0cf62b2e855b460deaedc17d1f276d6b3738458608f2e", + "cold_sha256": "12dcec5c696b989e0f78d76db6af8542104dec82e90b705269d53a8c52332871", + "reverse_sha256": "bc64aba71aba93596edff6ed270ea8ff17285c31cb79e357a5640edf6e934112", + "omitted_table_outline_size": false + } + ], + "native_author": "tools/native/formatted-insertion.ps1" +} diff --git a/corpus/formatted-insertion/native-cell/candidate/synthetic.one b/corpus/formatted-insertion/native-cell/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..28f0762ec463d819e00c9543089c830ccb592ad0 Binary files /dev/null and b/corpus/formatted-insertion/native-cell/candidate/synthetic.one differ diff --git a/corpus/formatted-insertion/native-cell/cold/notebook/synthetic.one b/corpus/formatted-insertion/native-cell/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..17aba94449e0a2d63c6e685530ca100eac36ddf1 Binary files /dev/null and b/corpus/formatted-insertion/native-cell/cold/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/native-cell/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/formatted-insertion/native-cell/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment new file mode 120000 index 0000000000000000000000000000000000000000..643deb5931b7cff2017653974a315859cebaff6a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment @@ -0,0 +1 @@ +../../../../append/round-01/native/append-01-complex/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/cold/read/environment.json b/corpus/formatted-insertion/native-cell/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..87488b3744764bed00abc7e6c3f19fd9eeb35d7d --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-382BC211", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/native-cell/cold/read/hierarchy.xml b/corpus/formatted-insertion/native-cell/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..e32e96bd390a20578794e47cb74802aedc895bef --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/native-cell/cold/read/page-000.xml b/corpus/formatted-insertion/native-cell/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..1f4ca301fb93b8076d16146e331c520316168da2 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/read/page-000.xml @@ -0,0 +1,21 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/native-cell/cold/read/payloads.json b/corpus/formatted-insertion/native-cell/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..42adb50e7552b696fff97155eb43f177b2455af0 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/read/payloads.json @@ -0,0 +1,10 @@ +[ + { + "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7", + "name": "fictitious-attachment.txt", + "object": "{A5A01D50-6C3A-4B67-9D41-B0CB3EEF1DF4}{11}{B0}", + "kind": "InsertedFile", + "page": "{2CEF1916-B4F8-48E4-9BD8-7292BDC39B61}{1}{B0}", + "bytes": 43 + } +] \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/cold/run.json b/corpus/formatted-insertion/native-cell/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..740c015f17cdad0bf3230e4d95f9e66ed2b5c2cf --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/native-cell", + "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": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/native-cell/cold/source.json b/corpus/formatted-insertion/native-cell/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..9adbf16c785dee7d9e5fab579f7d7a7b97ba9f9a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 27800, + "sha256": "2d01da4a2c8a0cba582d97460b708c77eca38617f9103f4a3cc9eea33f0e0351", + "mtime_ns": 1788842844345306887 + } +] diff --git a/corpus/formatted-insertion/native-cell/cold/teardown.json b/corpus/formatted-insertion/native-cell/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/native-cell/reverse/control-before.xml b/corpus/formatted-insertion/native-cell/reverse/control-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..4241dd8845f8db99f3f7046a27d2ff9db76b0b66 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/control-before.xml @@ -0,0 +1,21 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/native-cell/reverse/control-update.xml b/corpus/formatted-insertion/native-cell/reverse/control-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..0b3cff200e4b739992a164db9d2d7f9d0c6254da --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/control-update.xml @@ -0,0 +1,14 @@ +Bold 🦀 italic é color 東京
+End
]]>
\ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/reverse/edit-before.xml b/corpus/formatted-insertion/native-cell/reverse/edit-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..cbdd9391c0d01a439d2ef5aa7934eb73c6276a64 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/edit-before.xml @@ -0,0 +1,23 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/native-cell/reverse/edit-update.xml b/corpus/formatted-insertion/native-cell/reverse/edit-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..b5f3873d5efba57cbc999f303235e28d0b334c3b --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/edit-update.xml @@ -0,0 +1,16 @@ +<span style='font-weight:bold; +text-decoration:underline;font-family:Calibri;font-size:11.0pt' lang=en-US>Native Bold</span><span +style='font-family:Calibri;font-size:11.0pt' lang=en-US> </span><span +style='font-family:Calibri;font-size:13.5pt' lang=en-US>&#129408;</span><span +style='font-family:Calibri;font-size:11.0pt' lang=x-none> </span><span +style='font-style:italic;text-decoration:line-through;font-family:Calibri; +font-size:11.0pt' lang=x-none>italic</span><span style='font-family:Calibri; +font-size:11.0pt' lang=x-none> </span><span style='vertical-align:super; +font-family:Calibri;font-size:11.0pt' lang=x-none>é</span><span +style='font-family:Calibri;font-size:11.0pt' lang=x-none> </span><span +style='font-family:Calibri;font-size:11.0pt;color:#0C2238;background:yellow; +mso-highlight:yellow' lang=x-none>color</span><span style='font-family:Calibri; +font-size:11.0pt' lang=x-none> </span><span style='vertical-align:sub; +font-family:SimSun;font-size:11.0pt' lang=x-none>東京</span><span +style='font-family:Calibri;font-size:11.0pt' lang=en-US><br> +End</span> \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/reverse/notebook/synthetic.one b/corpus/formatted-insertion/native-cell/reverse/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..ed7adc9bbe97e090f46a41f4e4986c81b7e53114 Binary files /dev/null and b/corpus/formatted-insertion/native-cell/reverse/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/native-cell/reverse/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/formatted-insertion/native-cell/reverse/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment new file mode 120000 index 0000000000000000000000000000000000000000..643deb5931b7cff2017653974a315859cebaff6a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment @@ -0,0 +1 @@ +../../../../append/round-01/native/append-01-complex/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/reverse/read/environment.json b/corpus/formatted-insertion/native-cell/reverse/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..72756e1ebe3b45cfed4fbdc3be25c7bcc75c1902 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-0BC5CBA6", + "cold": false, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/native-cell/reverse/read/hierarchy.xml b/corpus/formatted-insertion/native-cell/reverse/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..8084310cac6090fc1620bfa0707523f867e3665d --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/native-cell/reverse/read/page-000.xml b/corpus/formatted-insertion/native-cell/reverse/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..a0471c21595e14c8a16ae9168f270e67166fa91c --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/read/page-000.xml @@ -0,0 +1,23 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=Native Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/native-cell/reverse/read/payloads.json b/corpus/formatted-insertion/native-cell/reverse/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..a32deb8d0773237cbef61e65e6ca206ab9c8cb6f --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/read/payloads.json @@ -0,0 +1,10 @@ +[ + { + "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7", + "name": "fictitious-attachment.txt", + "object": "{171BB2E8-2F2E-44E4-A1DA-8D153C23106A}{11}{B0}", + "kind": "InsertedFile", + "page": "{9E54B6AE-F7EC-4767-A743-4F4CBF0F96FF}{1}{B0}", + "bytes": 43 + } +] \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/reverse/reverse-verification.json b/corpus/formatted-insertion/native-cell/reverse/reverse-verification.json new file mode 100644 index 0000000000000000000000000000000000000000..bfac04a17d784c5af8031c413dfb20b497019831 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/reverse-verification.json @@ -0,0 +1,5 @@ +{ + "paragraphs": 5, + "characters": 112, + "exact_native_character_styles_after_control": true +} diff --git a/corpus/formatted-insertion/native-cell/reverse/run.json b/corpus/formatted-insertion/native-cell/reverse/run.json new file mode 100644 index 0000000000000000000000000000000000000000..3036799f2a1d714490c95f0bc60ca5ed81c09be6 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/run.json @@ -0,0 +1,19 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/native-cell", + "expected_pages": 1, + "author": "evidence/m10/formatted-reverse-auto-size.ps1", + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "author.ps1": "c5ebc74731fd4813803623b014ce9d6dbd6d2564918189865731807e6c458f9a", + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "df4244a18ab3e9c21083b8df61b08b863e8cce0ec4bf0c9cd742a8ee6f79877d" + } +} diff --git a/corpus/formatted-insertion/native-cell/reverse/source.json b/corpus/formatted-insertion/native-cell/reverse/source.json new file mode 100644 index 0000000000000000000000000000000000000000..9adbf16c785dee7d9e5fab579f7d7a7b97ba9f9a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 27800, + "sha256": "2d01da4a2c8a0cba582d97460b708c77eca38617f9103f4a3cc9eea33f0e0351", + "mtime_ns": 1788842844345306887 + } +] diff --git a/corpus/formatted-insertion/native-cell/reverse/teardown.json b/corpus/formatted-insertion/native-cell/reverse/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/reverse/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/native-cell/ui/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/formatted-insertion/native-cell/ui/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment new file mode 120000 index 0000000000000000000000000000000000000000..643deb5931b7cff2017653974a315859cebaff6a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/before-read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment @@ -0,0 +1 @@ +../../../../append/round-01/native/append-01-complex/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/ui/before-read/environment.json b/corpus/formatted-insertion/native-cell/ui/before-read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..d8ebe73f1d9519e534f42aad1326b1ef551cb38a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/before-read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-1DC21C3F", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/native-cell/ui/before-read/hierarchy.xml b/corpus/formatted-insertion/native-cell/ui/before-read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..6e1e0e211ba17dea4e64ab4dc59d03f02abf9728 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/before-read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/native-cell/ui/before-read/page-000.xml b/corpus/formatted-insertion/native-cell/ui/before-read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..2d0fb5a433b8cf70523be39d89cb71c799a16dc1 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/before-read/page-000.xml @@ -0,0 +1,21 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/native-cell/ui/before-read/payloads.json b/corpus/formatted-insertion/native-cell/ui/before-read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..7a7c79073c927aa687b6610b1725e9cb407a3dde --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/before-read/payloads.json @@ -0,0 +1,10 @@ +[ + { + "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7", + "name": "fictitious-attachment.txt", + "object": "{DB6763F6-1002-4C0E-A310-04011D46220A}{11}{B0}", + "kind": "InsertedFile", + "page": "{522867B0-C8C0-4F8D-A589-C6589E6AA49F}{1}{B0}", + "bytes": 43 + } +] \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/ui/edit.ahk b/corpus/formatted-insertion/native-cell/ui/edit.ahk new file mode 100644 index 0000000000000000000000000000000000000000..dea6451fe8769ca35fee08c4e69c255f33bf19f4 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/edit.ahk @@ -0,0 +1,10 @@ +#Requires AutoHotkey v2.0 +OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1))) +hwnd := WinExist("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE") +if !hwnd + throw Error("OneNote window is missing") +WinActivate(hwnd) +if !WinWaitActive(hwnd,, 5) + throw Error("OneNote window is not active") +Send "{Left}" +SendText "UI " diff --git a/corpus/formatted-insertion/native-cell/ui/edit.json b/corpus/formatted-insertion/native-cell/ui/edit.json new file mode 100644 index 0000000000000000000000000000000000000000..fb01ed19f95f03323b71f2a6163d84cfd209c255 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/edit.json @@ -0,0 +1,13 @@ +{ + "exit": 0, + "stdout": "", + "stderr": "", + "w": 1280, + "h": 720, + "error": null, + "win": { + "title": "Fictitious: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote", + "class": "Framework::CFrame", + "dialog": false + } +} \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/ui/edit.png b/corpus/formatted-insertion/native-cell/ui/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..af5911ca6cc6f2daf620c8d7e87d31ee0b8d3e12 Binary files /dev/null and b/corpus/formatted-insertion/native-cell/ui/edit.png differ diff --git a/corpus/formatted-insertion/native-cell/ui/navigate.ahk b/corpus/formatted-insertion/native-cell/ui/navigate.ahk new file mode 100644 index 0000000000000000000000000000000000000000..f1f505a5a2cbd9358d60c022a67ca454a941e7cd --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/navigate.ahk @@ -0,0 +1,20 @@ +#Requires AutoHotkey v2.0 +OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1))) +dm := Buffer(220, 0) +NumPut("UShort", 220, dm, 68) +if !DllCall("EnumDisplaySettingsW", "Ptr", 0, "UInt", 0xFFFFFFFF, "Ptr", dm) + throw Error("Cannot inspect display mode") +NumPut("UInt", NumGet(dm, 72, "UInt") | 0x180000, dm, 72) +NumPut("UInt", 1280, dm, 172) +NumPut("UInt", 720, dm, 176) +if DllCall("ChangeDisplaySettingsW", "Ptr", dm, "UInt", 0, "Int") != 0 + throw Error("Display mode rejected") +app := ComObject("OneNote.Application") +app.NavigateTo("{522867B0-C8C0-4F8D-A589-C6589E6AA49F}{1}{B0}", "{5F18EBB1-FBEF-079C-2689-78693371A9D4}{1}{B0}", false) +hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10) +if !hwnd + throw Error("OneNote window did not appear") +WinMaximize(hwnd) +WinActivate(hwnd) +if !WinWaitActive(hwnd,, 10) + throw Error("OneNote window did not become active") diff --git a/corpus/formatted-insertion/native-cell/ui/navigate.json b/corpus/formatted-insertion/native-cell/ui/navigate.json new file mode 100644 index 0000000000000000000000000000000000000000..fb01ed19f95f03323b71f2a6163d84cfd209c255 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/navigate.json @@ -0,0 +1,13 @@ +{ + "exit": 0, + "stdout": "", + "stderr": "", + "w": 1280, + "h": 720, + "error": null, + "win": { + "title": "Fictitious: caf\u00e9, \u6771\u4eac, \u0645\u0631\u062d\u0628\u0627 - Microsoft OneNote", + "class": "Framework::CFrame", + "dialog": false + } +} \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/ui/notebook/synthetic.one b/corpus/formatted-insertion/native-cell/ui/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..924b0e72abe4aaf362cdc259abc627fcfd021c0a Binary files /dev/null and b/corpus/formatted-insertion/native-cell/ui/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/native-cell/ui/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment b/corpus/formatted-insertion/native-cell/ui/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment new file mode 120000 index 0000000000000000000000000000000000000000..643deb5931b7cff2017653974a315859cebaff6a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment @@ -0,0 +1 @@ +../../../../append/round-01/native/append-01-complex/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/ui/read/environment.json b/corpus/formatted-insertion/native-cell/ui/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..01b16b980543732e06a632f9c2827d148608584e --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-1DC21C3F", + "cold": false, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/native-cell/ui/read/hierarchy.xml b/corpus/formatted-insertion/native-cell/ui/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..ed1afb9654c771d5286a0ca841451a26ef363857 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/native-cell/ui/read/page-000.xml b/corpus/formatted-insertion/native-cell/ui/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..e87283eb4d1986bb756b9fa5ec66eb7aefe9a07d --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/read/page-000.xml @@ -0,0 +1,21 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=UI Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/native-cell/ui/read/payloads.json b/corpus/formatted-insertion/native-cell/ui/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..7a7c79073c927aa687b6610b1725e9cb407a3dde --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/read/payloads.json @@ -0,0 +1,10 @@ +[ + { + "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7", + "name": "fictitious-attachment.txt", + "object": "{DB6763F6-1002-4C0E-A310-04011D46220A}{11}{B0}", + "kind": "InsertedFile", + "page": "{522867B0-C8C0-4F8D-A589-C6589E6AA49F}{1}{B0}", + "bytes": 43 + } +] \ No newline at end of file diff --git a/corpus/formatted-insertion/native-cell/ui/run.json b/corpus/formatted-insertion/native-cell/ui/run.json new file mode 100644 index 0000000000000000000000000000000000000000..d9c130e3a1e9e524610cb879fb0c17ce2c242bd6 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/corpus/formatted-insertion/native-cell/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": true, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "df4244a18ab3e9c21083b8df61b08b863e8cce0ec4bf0c9cd742a8ee6f79877d" + } +} diff --git a/corpus/formatted-insertion/native-cell/ui/source.json b/corpus/formatted-insertion/native-cell/ui/source.json new file mode 100644 index 0000000000000000000000000000000000000000..31fdab6427665482f7ae934711db6ed6c634b24a --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 27800, + "sha256": "2d01da4a2c8a0cba582d97460b708c77eca38617f9103f4a3cc9eea33f0e0351", + "mtime_ns": 1788844254905915500 + } +] diff --git a/corpus/formatted-insertion/native-cell/ui/teardown.json b/corpus/formatted-insertion/native-cell/ui/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/native-cell/ui/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/native-paragraph/candidate/synthetic.one b/corpus/formatted-insertion/native-paragraph/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..a694c2bd2cf955a627dd4c1b2fb02b5b3480904d Binary files /dev/null and b/corpus/formatted-insertion/native-paragraph/candidate/synthetic.one differ diff --git a/corpus/formatted-insertion/native-paragraph/cold/notebook/synthetic.one b/corpus/formatted-insertion/native-paragraph/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..31b88c12bd4e274c0343a60bd46c1f873e3708d6 Binary files /dev/null and b/corpus/formatted-insertion/native-paragraph/cold/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/native-paragraph/cold/read/environment.json b/corpus/formatted-insertion/native-paragraph/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..55c8c159d552592a4f276fa4192d060b0fb540f4 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-3A29493B", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/native-paragraph/cold/read/hierarchy.xml b/corpus/formatted-insertion/native-paragraph/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..7e8627c8f82f41a3adf20b024c34471fd76dfd90 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/native-paragraph/cold/read/page-000.xml b/corpus/formatted-insertion/native-paragraph/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..f9eb17b59d5e3c2f72ce8616cc84e85b8d65479d --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/read/page-000.xml @@ -0,0 +1,20 @@ + +Bold 🦀 italic é color 東京
+End
]]>
Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/formatted-insertion/native-paragraph/cold/read/payloads.json b/corpus/formatted-insertion/native-paragraph/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/native-paragraph/cold/run.json b/corpus/formatted-insertion/native-paragraph/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..90180cd6d148e07f8bdab094f2cf065a19ac7e98 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/native-paragraph", + "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": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/native-paragraph/cold/source.json b/corpus/formatted-insertion/native-paragraph/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..121a3ad6c342abe6014035e54a75ab17a9064003 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 18672, + "sha256": "202823512283be980b6a139adcf5f4f369d15f17d60ed7b23f9f2289513aa4e9", + "mtime_ns": 1788842844336692559 + } +] diff --git a/corpus/formatted-insertion/native-paragraph/cold/teardown.json b/corpus/formatted-insertion/native-paragraph/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/native-paragraph/reverse/control-before.xml b/corpus/formatted-insertion/native-paragraph/reverse/control-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..0472ef125290b184b05b9523541c744b14250d04 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/control-before.xml @@ -0,0 +1,20 @@ + +Bold 🦀 italic é color 東京
+End
]]>
Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/formatted-insertion/native-paragraph/reverse/control-update.xml b/corpus/formatted-insertion/native-paragraph/reverse/control-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..06fe75fb88aaf963f88327b7da7eb2150f0dbb90 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/control-update.xml @@ -0,0 +1,19 @@ +Bold 🦀 italic é color 東京
+End
]]>
Fictitious: café, 東京, مرحبا]]>
\ No newline at end of file diff --git a/corpus/formatted-insertion/native-paragraph/reverse/edit-before.xml b/corpus/formatted-insertion/native-paragraph/reverse/edit-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..55f4900f49e01ab52f424c48579719299ae56b60 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/edit-before.xml @@ -0,0 +1,22 @@ + +Bold 🦀 italic é color 東京
+End
]]>
Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/formatted-insertion/native-paragraph/reverse/edit-update.xml b/corpus/formatted-insertion/native-paragraph/reverse/edit-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..e35989ca0611eeb338fa00bd9c220bed23ceacf7 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/edit-update.xml @@ -0,0 +1,21 @@ +<span style='font-weight:bold; +text-decoration:underline;font-family:Calibri;font-size:11.0pt' lang=en-US>Native Bold</span><span +style='font-family:Calibri;font-size:11.0pt' lang=en-US> </span><span +style='font-family:Calibri;font-size:13.5pt' lang=en-US>&#129408;</span><span +style='font-family:Calibri;font-size:11.0pt' lang=x-none> </span><span +style='font-style:italic;text-decoration:line-through;font-family:Calibri; +font-size:11.0pt' lang=x-none>italic</span><span style='font-family:Calibri; +font-size:11.0pt' lang=x-none> </span><span style='vertical-align:super; +font-family:Calibri;font-size:11.0pt' lang=x-none>é</span><span +style='font-family:Calibri;font-size:11.0pt' lang=x-none> </span><span +style='font-family:Calibri;font-size:11.0pt;color:#0C2238;background:yellow; +mso-highlight:yellow' lang=x-none>color</span><span style='font-family:Calibri; +font-size:11.0pt' lang=x-none> </span><span style='vertical-align:sub; +font-family:SimSun;font-size:11.0pt' lang=x-none>東京</span><span +style='font-family:Calibri;font-size:11.0pt' lang=en-US><br> +End</span>Fictitious: café, 東京, مرحبا]]> \ No newline at end of file diff --git a/corpus/formatted-insertion/native-paragraph/reverse/notebook/synthetic.one b/corpus/formatted-insertion/native-paragraph/reverse/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..7add17ab4529e68d9371074e788420b883ce65a0 Binary files /dev/null and b/corpus/formatted-insertion/native-paragraph/reverse/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/native-paragraph/reverse/read/environment.json b/corpus/formatted-insertion/native-paragraph/reverse/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..c3570a3c1096cecee4afbfb86b94b3937a0f3765 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-E17E9EEE", + "cold": false, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/native-paragraph/reverse/read/hierarchy.xml b/corpus/formatted-insertion/native-paragraph/reverse/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..fcd19eaf5efbfaad3fe44fec03aced731f03937b --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/native-paragraph/reverse/read/page-000.xml b/corpus/formatted-insertion/native-paragraph/reverse/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..51769896f4e5a27849bc0ebfbcbf06ce46982c90 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/read/page-000.xml @@ -0,0 +1,22 @@ + +Native Bold 🦀 italic é color 東京
+End
]]>
Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/formatted-insertion/native-paragraph/reverse/read/payloads.json b/corpus/formatted-insertion/native-paragraph/reverse/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/native-paragraph/reverse/reverse-verification.json b/corpus/formatted-insertion/native-paragraph/reverse/reverse-verification.json new file mode 100644 index 0000000000000000000000000000000000000000..cb4c314d1a5da15e703f55d906b917c03eeefd20 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/reverse-verification.json @@ -0,0 +1,5 @@ +{ + "paragraphs": 2, + "characters": 63, + "exact_native_character_styles_after_control": true +} diff --git a/corpus/formatted-insertion/native-paragraph/reverse/run.json b/corpus/formatted-insertion/native-paragraph/reverse/run.json new file mode 100644 index 0000000000000000000000000000000000000000..8f8ea51728dcb5e8c8c3e8bd93f04c1efd279ae7 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/run.json @@ -0,0 +1,19 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/native-paragraph", + "expected_pages": 1, + "author": "evidence/m10/formatted-reverse-controlled.ps1", + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "author.ps1": "4362d0ba551eb00b2eca1c5563d76352469f85d3981155bf2682c159a03ddedb", + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/native-paragraph/reverse/source.json b/corpus/formatted-insertion/native-paragraph/reverse/source.json new file mode 100644 index 0000000000000000000000000000000000000000..121a3ad6c342abe6014035e54a75ab17a9064003 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 18672, + "sha256": "202823512283be980b6a139adcf5f4f369d15f17d60ed7b23f9f2289513aa4e9", + "mtime_ns": 1788842844336692559 + } +] diff --git a/corpus/formatted-insertion/native-paragraph/reverse/teardown.json b/corpus/formatted-insertion/native-paragraph/reverse/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/native-paragraph/reverse/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/outline/candidate/synthetic.one b/corpus/formatted-insertion/outline/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..0ae354b150488aa376c134c5ead70e300a9d2d1f Binary files /dev/null and b/corpus/formatted-insertion/outline/candidate/synthetic.one differ diff --git a/corpus/formatted-insertion/outline/cold/notebook/synthetic.one b/corpus/formatted-insertion/outline/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..8e2d6497c5eaba4154ad0d7fd37026dea435fca4 Binary files /dev/null and b/corpus/formatted-insertion/outline/cold/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/outline/cold/read/environment.json b/corpus/formatted-insertion/outline/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..e7b75b48ca08c4daefaa4768f0244be31adac1d3 --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-21E15CBC", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/outline/cold/read/hierarchy.xml b/corpus/formatted-insertion/outline/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..cf034ff1f30776969b7aaac5b1fd71db9f6317ae --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/outline/cold/read/page-000.xml b/corpus/formatted-insertion/outline/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..fdd3713cb7d3b3ac8d8ea565918e21d020a0aa7a --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/read/page-000.xml @@ -0,0 +1,15 @@ + +Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/outline/cold/read/payloads.json b/corpus/formatted-insertion/outline/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/outline/cold/run.json b/corpus/formatted-insertion/outline/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..bc688c6de22d1b61f2abcb62990dc7543ed5afdc --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/outline", + "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": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/outline/cold/source.json b/corpus/formatted-insertion/outline/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..97b70503780f0a46280992b2ae4ef6fe155f6896 --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 7776, + "sha256": "76f6b743caa9dc2b5ab342d6e6a742125ecbcd0ca2372062b42acbd08b5ebb4c", + "mtime_ns": 1788842844332249915 + } +] diff --git a/corpus/formatted-insertion/outline/cold/teardown.json b/corpus/formatted-insertion/outline/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/outline/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/formatted-insertion/outline/reverse/control-before.xml b/corpus/formatted-insertion/outline/reverse/control-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..9665eaf19e88ee23167cd355a640c0dc319656bb --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/control-before.xml @@ -0,0 +1,15 @@ + +Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/outline/reverse/control-update.xml b/corpus/formatted-insertion/outline/reverse/control-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..e004773441a3f300ddbb0f6841aedb5a870e3e0d --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/control-update.xml @@ -0,0 +1,14 @@ +Bold 🦀 italic é color 東京
+End
]]>
\ No newline at end of file diff --git a/corpus/formatted-insertion/outline/reverse/edit-before.xml b/corpus/formatted-insertion/outline/reverse/edit-before.xml new file mode 100644 index 0000000000000000000000000000000000000000..7962c00a827691815be37a776b035c3863d589dd --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/edit-before.xml @@ -0,0 +1,17 @@ + +Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/outline/reverse/edit-update.xml b/corpus/formatted-insertion/outline/reverse/edit-update.xml new file mode 100644 index 0000000000000000000000000000000000000000..60a96d8ad31eb5f904b19593565e2184ec8f9e92 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/edit-update.xml @@ -0,0 +1,16 @@ +<span style='font-weight:bold; +text-decoration:underline;font-family:Calibri;font-size:11.0pt' lang=en-US>Native Bold</span><span +style='font-family:Calibri;font-size:11.0pt' lang=en-US> </span><span +style='font-family:Calibri;font-size:13.5pt' lang=en-US>&#129408;</span><span +style='font-family:Calibri;font-size:11.0pt' lang=x-none> </span><span +style='font-style:italic;text-decoration:line-through;font-family:Calibri; +font-size:11.0pt' lang=x-none>italic</span><span style='font-family:Calibri; +font-size:11.0pt' lang=x-none> </span><span style='vertical-align:super; +font-family:Calibri;font-size:11.0pt' lang=x-none>é</span><span +style='font-family:Calibri;font-size:11.0pt' lang=x-none> </span><span +style='font-family:Calibri;font-size:11.0pt;color:#0C2238;background:yellow; +mso-highlight:yellow' lang=x-none>color</span><span style='font-family:Calibri; +font-size:11.0pt' lang=x-none> </span><span style='vertical-align:sub; +font-family:SimSun;font-size:11.0pt' lang=x-none>東京</span><span +style='font-family:Calibri;font-size:11.0pt' lang=en-US><br> +End</span> \ No newline at end of file diff --git a/corpus/formatted-insertion/outline/reverse/notebook/synthetic.one b/corpus/formatted-insertion/outline/reverse/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..e5a75cf4cf8beb25339ea2c0a9f01d802bf37166 Binary files /dev/null and b/corpus/formatted-insertion/outline/reverse/notebook/synthetic.one differ diff --git a/corpus/formatted-insertion/outline/reverse/read/environment.json b/corpus/formatted-insertion/outline/reverse/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..943cae9509c3b41401e79b1e2623b234808dbf37 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-129C20F7", + "cold": false, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/formatted-insertion/outline/reverse/read/hierarchy.xml b/corpus/formatted-insertion/outline/reverse/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..306e4d27131ed097fcfe338c5dec32e9e8536064 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/formatted-insertion/outline/reverse/read/page-000.xml b/corpus/formatted-insertion/outline/reverse/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..127576e2c2620bcf5d0c5f4827dfddf8b8b40710 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/read/page-000.xml @@ -0,0 +1,17 @@ + +Native Bold 🦀 italic é color 東京
+End
]]>
diff --git a/corpus/formatted-insertion/outline/reverse/read/payloads.json b/corpus/formatted-insertion/outline/reverse/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/formatted-insertion/outline/reverse/reverse-verification.json b/corpus/formatted-insertion/outline/reverse/reverse-verification.json new file mode 100644 index 0000000000000000000000000000000000000000..071812591fea6f0fadd5972162733ab1d5372e56 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/reverse-verification.json @@ -0,0 +1,5 @@ +{ + "paragraphs": 2, + "characters": 44, + "exact_native_character_styles_after_control": true +} diff --git a/corpus/formatted-insertion/outline/reverse/run.json b/corpus/formatted-insertion/outline/reverse/run.json new file mode 100644 index 0000000000000000000000000000000000000000..5cf1f1fe27801601e6711caae16edd4f87bf725e --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/run.json @@ -0,0 +1,19 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/formatted-insertion-isolated/outline", + "expected_pages": 1, + "author": "evidence/m10/formatted-reverse-controlled.ps1", + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "author.ps1": "4362d0ba551eb00b2eca1c5563d76352469f85d3981155bf2682c159a03ddedb", + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "086789e467779add0fc071ad8c524c2b02d3d0d609aea4a41f3128cadba371e8" + } +} diff --git a/corpus/formatted-insertion/outline/reverse/source.json b/corpus/formatted-insertion/outline/reverse/source.json new file mode 100644 index 0000000000000000000000000000000000000000..97b70503780f0a46280992b2ae4ef6fe155f6896 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 7776, + "sha256": "76f6b743caa9dc2b5ab342d6e6a742125ecbcd0ca2372062b42acbd08b5ebb4c", + "mtime_ns": 1788842844332249915 + } +] diff --git a/corpus/formatted-insertion/outline/reverse/teardown.json b/corpus/formatted-insertion/outline/reverse/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/formatted-insertion/outline/reverse/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/crates/onestore-offline/README.md b/crates/onestore-offline/README.md index e338af2d44764e80cdeb7ad5c72c062cb0648f76..bae1c8bdb444b6cdb1156697916e728d9da5077f 100644 --- a/crates/onestore-offline/README.md +++ b/crates/onestore-offline/README.md @@ -26,7 +26,8 @@ assert_eq!(pending.last().map(|edit| edit.id), local_id); ``` `insert` accepts the core library's `Insertion` value and durably retains its -object identities. Keep that value across retries; its `text_object()` identifies +intent identities. `Insertion::with_formatting` queues text and +its styles as one intent and one publication. Keep that value across retries; its `text_object()` identifies the new text for subsequent offline edits. Pending entries expose `Operation::Text(TextEdit)`, `Operation::Insert(Insertion)` or `Operation::Format(FormatEdit)` through their @@ -47,12 +48,9 @@ use onestore::{ExGuid, Insertion, TextAttribute}; use onestore_offline::{EditStatus, Replica}; # fn add_outline(cache: &Replica, space: ExGuid, page: ExGuid) # -> Result<(), Box> { -let outline = Insertion::outline(page, 144.0, 216.0, "Offline outline", "Author")?; +let outline = Insertion::outline(page, 144.0, 216.0, "Offline outline", "Author")? + .with_formatting(0..7, &[TextAttribute::Bold(true)])?; let id = cache.insert(&cache.snapshot()?, space, &outline)?; -cache.format( - &cache.snapshot()?, space, outline.text_object(), 0..7, - &[TextAttribute::Bold(true)], -)?; if let Some(id) = id { // A running worker may already have advanced this state. match cache.status(id)? { diff --git a/crates/onestore-offline/examples/smb_offline_client.rs b/crates/onestore-offline/examples/smb_offline_client.rs index db8bfbcfd7a648c3e465600572ba36c6bbe2ff9c..4cea5e7d44f8dd4ffd85d0db8a51f20d8bc4832d 100644 --- a/crates/onestore-offline/examples/smb_offline_client.rs +++ b/crates/onestore-offline/examples/smb_offline_client.rs @@ -301,7 +301,14 @@ fn queue_document( &text, "Offline document writer", )? - }; + } + .with_formatting( + 0..u32::try_from(text.encode_utf16().count())?, + &[ + TextAttribute::FontSize(13.5), + TextAttribute::Color(Some([0x44, 0x55, 0x66])), + ], + )?; let parent = if operation.is_multiple_of(2) { insertion.object() } else { diff --git a/crates/onestore-offline/tests/cache.rs b/crates/onestore-offline/tests/cache.rs index 31f727a97413e28cc255bcae111b8ef8bc8f580a..a0f9025d8e4b3f02e6fc53ef6d45e8b9fe0aad2b 100644 --- a/crates/onestore-offline/tests/cache.rs +++ b/crates/onestore-offline/tests/cache.rs @@ -609,7 +609,9 @@ fn twelve_local_clients_preserve_inserted_identities_and_dependent_edits() { "Author", ) .unwrap() - }; + } + .with_formatting(0..6, &[onestore::TextAttribute::Italic(true)]) + .unwrap(); loop { assert!( Instant::now() < deadline, @@ -691,6 +693,17 @@ fn twelve_local_clients_preserve_inserted_identities_and_dependent_edits() { format!("Edited Paragraph {client}:{sequence}") }; assert!(matches!(&v.nodes[id].kind,Kind::RichText{text,..} if *text==wanted)); + let runs = v.text_runs(*id).unwrap(); + assert_eq!(runs[0].format.italic, Some(true)); + assert_eq!( + runs[0].text, + if sequence == 0 { + "Client" + } else { + "Edited Paragr" + } + ); + assert!(runs[1..].iter().all(|run| run.format.italic != Some(true))); } } } diff --git a/crates/onestore-offline/tests/sync.rs b/crates/onestore-offline/tests/sync.rs index 5128b17829f84f71916c3c548d437ee866feca4b..8cc2aada4ffffdfa93bcb5e201d3e640ef9579eb 100644 --- a/crates/onestore-offline/tests/sync.rs +++ b/crates/onestore-offline/tests/sync.rs @@ -1736,8 +1736,10 @@ fn offline_insertions_survive_reopen_rebase_and_dependent_text_edits() { let doc = Document::parse(&index).unwrap(); let (_, page) = doc.pages().unwrap()[0]; let cache = Replica::create(&path, &source).unwrap(); - let outline = - Insertion::outline(page, 72.0, 144.0, "Offline outline", "Offline author").unwrap(); + let outline = Insertion::outline(page, 72.0, 144.0, "Offline outline", "Offline author") + .unwrap() + .with_formatting(0..7, &[onestore::TextAttribute::Italic(true)]) + .unwrap(); let first = cache.insert(&source, sid, &outline).unwrap().unwrap(); let snapshot = cache.snapshot().unwrap(); let paragraph = Insertion::paragraph( @@ -1746,6 +1748,8 @@ fn offline_insertions_survive_reopen_rebase_and_dependent_text_edits() { "Offline paragraph 🦀", "Offline author", ) + .unwrap() + .with_formatting(8..17, &[onestore::TextAttribute::Bold(true)]) .unwrap(); let second = cache.insert(&snapshot, sid, ¶graph).unwrap().unwrap(); let third = cache @@ -1786,6 +1790,12 @@ fn offline_insertions_survive_reopen_rebase_and_dependent_text_edits() { ] { assert!(matches!(&v.nodes[&id].kind,Kind::RichText{text,..} if text==wanted)); } + let outline_runs = v.text_runs(outline.text_object()).unwrap(); + assert_eq!(outline_runs[0].text, "Offline"); + assert_eq!(outline_runs[0].format.italic, Some(true)); + let runs = v.text_runs(paragraph.text_object()).unwrap(); + assert_eq!(runs[1].text, "paragraph"); + assert_eq!(runs[1].format.bold, Some(true)); assert_eq!( v.nodes[&outline.object()].children.last(), Some(¶graph.object()) @@ -1811,8 +1821,12 @@ fn uncertain_insertions_reconcile_the_original_revision_without_duplicate_object let doc = Document::parse(&index).unwrap(); let (sid, page) = doc.pages().unwrap()[0]; let cache = Replica::create(&path, &source).unwrap(); - let insertion = - Insertion::outline(page, 144.0, 144.0, "Uncertain insertion", "Author").unwrap(); + let insertion = Insertion::outline(page, 144.0, 144.0, "Uncertain insertion", "Author") + .unwrap() + .with_formatting(0..9, &[onestore::TextAttribute::Bold(true)]) + .unwrap() + .with_formatting(10..19, &[onestore::TextAttribute::Italic(true)]) + .unwrap(); let id = cache.insert(&source, sid, &insertion).unwrap().unwrap(); let local = cache.snapshot().unwrap(); let mut server = Server::new(&source); @@ -1860,6 +1874,15 @@ fn uncertain_insertions_reconcile_the_original_revision_without_duplicate_object let v = &s.revisions[&s.contexts[&ExGuid::default()]]; assert_eq!(v.nodes.values().filter(|node|matches!(&node.kind,Kind::RichText{text,..} if text=="Uncertain insertion")).count(),1); assert!(v.nodes.contains_key(&insertion.object())); + let runs = v.text_runs(insertion.text_object()).unwrap(); + assert_eq!(runs.len(), 3); + assert_eq!(runs[0].text, "Uncertain"); + assert_eq!(runs[0].format.bold, Some(true)); + assert_eq!(runs[1].text, " "); + assert_ne!(runs[1].format.bold, Some(true)); + assert_ne!(runs[1].format.italic, Some(true)); + assert_eq!(runs[2].text, "insertion"); + assert_eq!(runs[2].format.italic, Some(true)); assert!(cache.pending().unwrap().is_empty()); } } @@ -2668,7 +2691,9 @@ fn reviewed_insertion_placements_preserve_identity_and_dependent_operations() { Insertion::outline(page, 144.0, 144.0, "Offline", "Author").unwrap() } else { Insertion::paragraph(parent, Some(anchor.object()), "Offline", "Author").unwrap() - }; + } + .with_formatting(0..7, &[A::Italic(true)]) + .unwrap(); let cache = Replica::create(&path, &source).unwrap(); let first = cache.insert(&source, sid, &insertion).unwrap().unwrap(); let second = cache @@ -2754,6 +2779,10 @@ fn reviewed_insertion_placements_preserve_identity_and_dependent_operations() { }; assert_eq!(rebased.object(), insertion.object()); assert_eq!(rebased.text_object(), insertion.text_object()); + assert_eq!( + serde_json::to_value(&rebased).unwrap()["formats"], + serde_json::to_value(&insertion).unwrap()["formats"] + ); assert_eq!(cache.status(first).unwrap(), Some(EditStatus::Pending)); drop(cache); let cache = Replica::open(&path).unwrap(); @@ -2775,6 +2804,7 @@ fn reviewed_insertion_placements_preserve_identity_and_dependent_operations() { "Offline 🦀" ); assert!(runs.iter().all(|r| r.format.bold == Some(true))); + assert!(runs.iter().all(|r| r.format.italic == Some(true))); if outline_case { assert_eq!( ( diff --git a/crates/onestore/README.md b/crates/onestore/README.md index d6344cf19746fb4dc9617dbb7fdaf67d20d22a08..77cf64f3c7bd8e0d0e9545af30ad7fd043f03a7b 100644 --- a/crates/onestore/README.md +++ b/crates/onestore/README.md @@ -71,7 +71,9 @@ passwords, unsupported protection profiles and work-limit failures remain distin The source stays encrypted; protected writes are rejected. Insertions update child references, reference counts, modification times and automatic titles atomically. Paragraphs can be nested or inserted into table cells; outline -coordinates use points. Retain the `Insertion` value for rebasing: creating another +coordinates use points. `Insertion::with_formatting` includes nonoverlapping UTF-16 +spans in that same publication; gaps keep the default insertion style. Empty text +accepts one `0..0` span for subsequent typing. Retain the `Insertion` value for rebasing: creating another value creates different object identities. Duplicate insertion identities require reconciliation. Formatting accepts explicit attributes, preserves inherited values, and gives retired immutable styles zero current references while retaining history. diff --git a/crates/onestore/src/formatting.rs b/crates/onestore/src/formatting.rs index 5d40d1eff4a55274dd75a8889253507376d898d8..2056257ebc1c7ec761798c5e611542230379929d 100644 --- a/crates/onestore/src/formatting.rs +++ b/crates/onestore/src/formatting.rs @@ -91,26 +91,7 @@ pub(crate) fn format_text( "Select a text range and at least one formatting attribute", )); } - let mut values = Vec::new(); - let mut seen = BTreeSet::new(); - for attribute in attributes { - let (id, value) = attribute.property()?; - if !seen.insert(id & 0x7fffffff) { - return Err(invalid("Specify each formatting attribute once")); - } - values.push((id, value)); - } - if attributes.contains(&TextAttribute::Superscript(true)) - && attributes.contains(&TextAttribute::Subscript(true)) - { - return Err(invalid("Text cannot be both superscript and subscript")); - } - // Setting either script position clears its mutually exclusive counterpart. - for (set, opposite) in [(0x88001c08, 0x08001c09), (0x88001c09, 0x08001c08)] { - if values.iter().any(|(id, _)| *id == set) && !seen.contains(&opposite) { - values.push((opposite, Vec::new())); - } - } + let values = attribute_values(attributes)?; let store = Store::parse(source)?; let index = RevisionIndex::parse(&store)?; index.validate_current()?; @@ -286,3 +267,30 @@ pub(crate) fn format_text( Ok(changed) }) } + +pub(crate) fn attribute_values(attributes: &[TextAttribute]) -> Result)>, Error> { + if attributes.is_empty() { + return Err(invalid("Choose at least one formatting attribute")); + } + let mut values = Vec::new(); + let mut seen = BTreeSet::new(); + for attribute in attributes { + let (id, value) = attribute.property()?; + if !seen.insert(id & 0x7fffffff) { + return Err(invalid("Specify each formatting attribute once")); + } + values.push((id, value)); + } + if attributes.contains(&TextAttribute::Superscript(true)) + && attributes.contains(&TextAttribute::Subscript(true)) + { + return Err(invalid("Text cannot be both superscript and subscript")); + } + // Setting either script position clears its mutually exclusive counterpart. + for (set, opposite) in [(0x88001c08, 0x08001c09), (0x88001c09, 0x08001c08)] { + if values.iter().any(|(id, _)| *id == set) && !seen.contains(&opposite) { + values.push((opposite, Vec::new())); + } + } + Ok(values) +} diff --git a/crates/onestore/src/insertion.rs b/crates/onestore/src/insertion.rs index a6589165b4ecf1acadb90afd88f54c1bcd23c3e5..8543533ec3a70cf6c945fcc7ebc7241aedc8d6ed 100644 --- a/crates/onestore/src/insertion.rs +++ b/crates/onestore/src/insertion.rs @@ -8,6 +8,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, + ops::Range, sync::Arc, }; @@ -22,6 +23,14 @@ enum Placement { Outline { x: f32, y: f32 }, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct FormatSpan { + guid: [u8; 16], + range: Range, + attributes: Vec, +} + /// A paragraph or outline insertion with stable object identities and creation time. /// Retain this intent across rebases; constructing another intent allocates different identities. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -33,9 +42,29 @@ pub struct Insertion { text: String, author: String, created: u32, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + formats: Vec, } impl Insertion { + /// Adds a nonoverlapping UTF-16 formatting span, retaining all existing identities. + /// Gaps use the ordinary insertion style; only empty text accepts a zero-length span. + pub fn with_formatting( + &self, + range: Range, + attributes: &[crate::TextAttribute], + ) -> Result { + let mut intent = self.clone(); + intent.formats.push(FormatSpan { + guid: fresh_guid()?, + range, + attributes: attributes.to_vec(), + }); + intent.formats.sort_by_key(|span| span.range.start); + intent.validate()?; + Ok(intent) + } + /// Inserts before a direct child, or appends when `before` is None. /// The parent must be an editable outline, paragraph, outline group or table cell. /// Carriage returns represent soft line breaks; line feeds and embedded-field markers are rejected. @@ -89,6 +118,7 @@ impl Insertion { text: text.to_owned(), author: author.to_owned(), created: current_timestamps()?.0, + formats: Vec::new(), }; intent.validate()?; Ok(intent) @@ -125,6 +155,37 @@ impl Insertion { { return Err(invalid("Outline coordinates must be finite")); } + if !self.formats.is_empty() { + let units: Vec<_> = self.text.encode_utf16().collect(); + let length = u32::try_from(units.len()) + .map_err(|_| invalid("Text exceeds UTF-16 offset range"))?; + let mut identities = BTreeSet::from([self.guid]); + let mut end = 0; + for span in &self.formats { + if span.guid == [0; 16] || !identities.insert(span.guid) { + return Err(invalid("Formatting identities must be distinct")); + } + if span.range.start > span.range.end + || span.range.end > length + || span.range.start < end + || (span.range.is_empty() && (!units.is_empty() || self.formats.len() != 1)) + { + return Err(invalid( + "Formatting spans must be nonoverlapping ranges within the new text", + )); + } + for boundary in [span.range.start, span.range.end] { + if boundary > 0 + && boundary < length + && (0xd800..=0xdbff).contains(&units[boundary as usize - 1]) + { + return Err(invalid("Formatting boundary splits a surrogate pair")); + } + } + crate::formatting::attribute_values(&span.attributes)?; + end = span.range.end; + } + } Ok(()) } @@ -243,6 +304,55 @@ impl Insertion { }, ); } + if !self.formats.is_empty() { + let default = ExGuid { + guid: self.guid, + n: 5, + }; + let mut segments = Vec::new(); + let mut end = 0; + for span in &self.formats { + if end < span.range.start { + segments.push((span.range.start, default)); + } + let id = ExGuid { + guid: span.guid, + n: 1, + }; + let mut style = PropertyObject { + jcid: 0x12004d, + bytes: properties(&default_text_style())?, + global_ids: Arc::new(BTreeMap::from([(0, span.guid)])), + }; + let values = crate::formatting::attribute_values(&span.attributes)?; + style.set( + &values + .iter() + .map(|(id, value)| (*id, value.as_slice())) + .collect::>(), + )?; + new.insert(id, style); + segments.push((span.range.end, id)); + end = span.range.end; + } + let length = u32::try_from(self.text.encode_utf16().count()) + .map_err(|_| invalid("Text exceeds UTF-16 offset range"))?; + if end < length { + segments.push((length, default)); + } + if !segments.iter().any(|(_, id)| *id == default) { + new.remove(&default); + } + let target = new.get_mut(&self.text_object()).unwrap(); + let mut references = Vec::new(); + let mut ends = Vec::new(); + for (end, id) in segments { + references.extend_from_slice(&target.reference(id)?); + ends.extend_from_slice(&end.to_le_bytes()); + } + ends.truncate(ends.len() - 4); + target.set(&[(0x24001e13, &references), (0x1c001e12, &ends)])?; + } if let Placement::Outline { x, y } = self.placement { new.insert( self.object(), diff --git a/crates/onestore/tests/insertion.rs b/crates/onestore/tests/insertion.rs index 379b11a674a504f638b945b472cd855f1137e54b..2b215f02768379f4a4961f46f47f90f3c1e68097 100644 --- a/crates/onestore/tests/insertion.rs +++ b/crates/onestore/tests/insertion.rs @@ -6,7 +6,7 @@ mod current; mod disk; use onestore::{ - ExGuid, Insertion, PreparedEdit, RevisionIndex, Store, + ExGuid, Insertion, PreparedEdit, RevisionIndex, Store, TextAttribute as A, document::{Document, Kind}, }; @@ -28,6 +28,335 @@ fn targets(source: &[u8]) -> (ExGuid, ExGuid, ExGuid, ExGuid, ExGuid) { (sid, page, outline, paragraph, text) } +#[test] +fn atomic_formatted_insertions_match_a_character_model_and_preserve_history() { + use serde_json::json; + let source = onestore::create_section("rich.one", "Original", "Author").unwrap(); + let (sid, page, outline, _, _) = targets(&source); + let text = "ab🦀 e\u{301} 東京\rEnd\t!"; + let plain = Insertion::paragraph(outline, None, text, "Author").unwrap(); + assert!( + serde_json::to_value(&plain) + .unwrap() + .get("formats") + .is_none() + ); + let restored: Insertion = serde_json::from_slice(&serde_json::to_vec(&plain).unwrap()).unwrap(); + assert_eq!(plain, restored); + let base = PreparedEdit::insert(&source, sid, &plain).unwrap(); + let base_store = Store::parse(base.as_bytes()).unwrap(); + let base_index = RevisionIndex::parse(&base_store).unwrap(); + let base_doc = Document::parse(&base_index).unwrap(); + let base_space = &base_doc.spaces[&sid]; + let base_view = &base_space.revisions[&base_space.contexts[&ExGuid::default()]]; + let default = + serde_json::to_value(&base_view.text_runs(plain.text_object()).unwrap()[0].format).unwrap(); + let offsets: Vec = std::iter::once(0) + .chain(text.chars().scan(0, |n, c| { + *n += c.len_utf16() as u32; + Some(*n) + })) + .collect(); + let old_store = Store::parse(&source).unwrap(); + let old_index = RevisionIndex::parse(&old_store).unwrap(); + for seed in 1..=48_u64 { + let mut random = seed; + let mut expected: Vec<_> = text.chars().map(|c| (c, default.clone())).collect(); + let mut intent = if seed % 2 == 0 { + Insertion::outline(page, 72.0, 144.0, text, "Author").unwrap() + } else { + plain.clone() + }; + // Descending construction also exercises persisted ordering independently of call order. + for i in (0..expected.len()).rev() { + random ^= random << 13; + random ^= random >> 7; + random ^= random << 17; + let (attributes, values) = match random % 8 { + 0 => continue, + 1 => ( + vec![A::Bold(true), A::Underline(true)], + json!({"bold":true,"underline":true}), + ), + 2 => ( + vec![A::Italic(true), A::Strike(true)], + json!({"italic":true,"strike":true}), + ), + 3 => ( + vec![A::Font("Arial".into()), A::FontSize(13.5)], + json!({"font":"Arial","font_size":13.5}), + ), + 4 => ( + vec![ + A::Color(Some([12, 34, 56])), + A::Highlight(Some([255, 255, 0])), + ], + json!({"color":0x38220c,"highlight":0x00ffff}), + ), + 5 => ( + vec![A::Superscript(true)], + json!({"superscript":true,"subscript":false}), + ), + 6 => ( + vec![A::Subscript(true)], + json!({"subscript":true,"superscript":false}), + ), + _ => ( + vec![A::Bold(false), A::Color(None), A::Highlight(None)], + json!({"bold":false,"color":0xff000000_u32,"highlight":0xff000000_u32}), + ), + }; + intent = intent + .with_formatting(offsets[i]..offsets[i + 1], &attributes) + .unwrap(); + for (key, value) in values.as_object().unwrap() { + expected[i].1[key] = value.clone(); + } + } + let restored: Insertion = + serde_json::from_slice(&serde_json::to_vec(&intent).unwrap()).unwrap(); + assert_eq!(restored, intent); + let edited = PreparedEdit::insert(&source, sid, &restored).unwrap(); + let store = Store::parse(edited.as_bytes()).unwrap(); + assert_eq!( + store.header.transaction_count, + old_store.header.transaction_count + 1 + ); + let index = RevisionIndex::parse(&store).unwrap(); + index.validate_current().unwrap(); + assert_eq!( + index.spaces[&sid].revisions.len(), + old_index.spaces[&sid].revisions.len() + 1 + ); + for (space, old) in &old_index.spaces { + for revision in old.revisions.keys() { + assert_eq!( + format!("{:?}", old_index.resolve(*space, *revision).unwrap()), + format!("{:?}", index.resolve(*space, *revision).unwrap()) + ); + } + } + let document = Document::parse(&index).unwrap(); + let space = &document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + let actual: Vec<_> = view + .text_runs(intent.text_object()) + .unwrap() + .into_iter() + .flat_map(|run| { + let format = serde_json::to_value(run.format).unwrap(); + run.text.chars().map(move |c| (c, format.clone())) + }) + .collect(); + assert_eq!(actual, expected, "seed {seed}"); + } +} + +#[test] +fn invalid_or_forged_insertion_formats_fail_before_source_parsing() { + use serde_json::json; + let source = onestore::create_section("invalid-rich.one", "Original", "Author").unwrap(); + let (sid, _, outline, _, _) = targets(&source); + let plain = Insertion::paragraph(outline, None, "a🦀e\u{301}b", "Author").unwrap(); + for (start, end) in [(0, 0), (2, 3), (1, 2), (4, 3), (0, 7)] { + let range = start..end; + assert!(plain.with_formatting(range, &[A::Bold(true)]).is_err()); + } + for attributes in [ + vec![], + vec![A::Bold(true), A::Bold(false)], + vec![A::FontSize(f32::NAN)], + vec![A::FontSize(5.5)], + vec![A::Font("a\0b".into())], + vec![A::Superscript(true), A::Subscript(true)], + ] { + assert!(plain.with_formatting(1..3, &attributes).is_err()); + } + let formatted = plain.with_formatting(1..3, &[A::Bold(true)]).unwrap(); + for range in [0..3, 1..3, 1..4] { + assert!( + formatted + .with_formatting(range, &[A::Italic(true)]) + .is_err() + ); + } + let encoded = serde_json::to_value(&formatted).unwrap(); + for mutate in 0..6 { + let mut value = encoded.clone(); + match mutate { + 0 => value["formats"][0]["guid"] = serde_json::to_value([0; 16]).unwrap(), + 1 => value["formats"][0]["guid"] = value["guid"].clone(), + 2 => { + let duplicate = value["formats"][0].clone(); + value["formats"].as_array_mut().unwrap().push(duplicate); + } + 3 => value["formats"][0]["range"] = json!({"start":2,"end":3}), + 4 => value["formats"][0]["attributes"] = json!([]), + _ => value["formats"][0]["range"] = json!({"start":0,"end":u32::MAX}), + } + let forged: Insertion = serde_json::from_value(value).unwrap(); + let error = PreparedEdit::insert(&[], sid, &forged).err().unwrap(); + assert_eq!( + error, + PreparedEdit::insert(&source, sid, &forged).err().unwrap() + ); + } +} + +#[test] +fn formatted_empty_insertions_preserve_the_insertion_style_for_later_typing() { + let source = onestore::create_section("empty-rich.one", "Original", "Author").unwrap(); + let (sid, page, outline, _, _) = targets(&source); + for plain in [ + Insertion::outline(page, 72.0, 144.0, "", "Author").unwrap(), + Insertion::paragraph(outline, None, "", "Author").unwrap(), + ] { + let intent = plain + .with_formatting(0..0, &[A::Italic(true), A::FontSize(18.0)]) + .unwrap(); + assert!(intent.with_formatting(0..0, &[A::Bold(true)]).is_err()); + let inserted = PreparedEdit::insert(&source, sid, &intent).unwrap(); + let typed = PreparedEdit::text( + inserted.as_bytes(), + sid, + intent.text_object(), + 0..0, + "Typed 🦀", + ) + .unwrap(); + for (bytes, text) in [(inserted.as_bytes(), ""), (typed.as_bytes(), "Typed 🦀")] { + let store = Store::parse(bytes).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + index.validate_current().unwrap(); + let document = Document::parse(&index).unwrap(); + let space = &document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + let runs = view.text_runs(intent.text_object()).unwrap(); + assert_eq!(runs.len(), 1); + assert_eq!(runs[0].text, text); + assert_eq!(runs[0].format.italic, Some(true)); + assert_eq!(runs[0].format.font_size, Some(18.0)); + } + } +} + +#[test] +#[ignore = "exports formatted insertion candidates for cold native validation"] +fn export_native_formatted_insertions() { + use std::{fs, path::PathBuf}; + let output = PathBuf::from(std::env::var_os("ONESTORE_INSERT_OUTPUT").unwrap()); + assert!(output.is_absolute()); + fs::create_dir(&output).unwrap(); + let generated = onestore::create_section("rich.one", "Original", "Author").unwrap(); + let unicode = include_bytes!( + "../../../corpus/native/20260905-05/snapshots/03-format-unicode/notebook/synthetic.one" + ); + let table = include_bytes!( + "../../../corpus/native/20260905-05/snapshots/07-table/notebook/synthetic.one" + ); + let text = "Bold 🦀 italic e\u{301} color 東京\rEnd"; + let mut manifest = Vec::new(); + for (name, source, empty) in [ + ("outline", generated.as_slice(), false), + ("native-paragraph", unicode.as_slice(), false), + ("native-cell", table.as_slice(), false), + ("empty", generated.as_slice(), true), + ("empty-typed", generated.as_slice(), true), + ] { + let (sid, page, outline, paragraph, _) = targets(source); + let insertion = match name { + "native-paragraph" => { + Insertion::paragraph(outline, Some(paragraph), text, "Rich author") + } + "native-cell" => { + let store = Store::parse(source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let space = &document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + let cell = *view + .nodes + .iter() + .find(|(_, node)| matches!(node.kind, Kind::Cell { .. })) + .unwrap() + .0; + Insertion::paragraph(cell, None, text, "Rich author") + } + _ => Insertion::outline( + page, + 72.0, + 144.0, + if empty { "" } else { text }, + "Rich author", + ), + } + .unwrap(); + let mut intent = insertion; + if empty { + intent = intent + .with_formatting(0..0, &[A::Italic(true), A::FontSize(18.0)]) + .unwrap(); + } else { + for (label, attributes) in [ + ("Bold", vec![A::Bold(true), A::Underline(true)]), + ("🦀", vec![A::Font("Arial".into()), A::FontSize(13.5)]), + ("italic", vec![A::Italic(true), A::Strike(true)]), + ("e\u{301}", vec![A::Superscript(true)]), + ( + "color", + vec![ + A::Color(Some([12, 34, 56])), + A::Highlight(Some([255, 255, 0])), + ], + ), + ("東京", vec![A::Subscript(true)]), + ( + "End", + vec![ + A::Bold(false), + A::Italic(false), + A::Color(None), + A::Highlight(None), + ], + ), + ] { + let start = text[..text.find(label).unwrap()].encode_utf16().count() as u32; + intent = intent + .with_formatting( + start..start + label.encode_utf16().count() as u32, + &attributes, + ) + .unwrap(); + } + } + let inserted = PreparedEdit::insert(source, sid, &intent).unwrap(); + let bytes = if name == "empty-typed" { + PreparedEdit::text( + inserted.as_bytes(), + sid, + intent.text_object(), + 0..0, + "Typed café 🦀", + ) + .unwrap() + .as_bytes() + .to_vec() + } else { + inserted.as_bytes().to_vec() + }; + current::current(&bytes); + let notebook = output.join(name); + fs::create_dir(¬ebook).unwrap(); + fs::write(notebook.join("synthetic.one"), &bytes).unwrap(); + manifest.push(serde_json::json!({"name":name,"space":sid,"intent":intent})); + } + fs::write( + output.join("manifest.json"), + serde_json::to_vec_pretty(&manifest).unwrap(), + ) + .unwrap(); +} + #[test] fn paragraph_and_outline_insertions_publish_metadata_and_references_together() { let source = onestore::create_section("insertion.one", "Original", "Original author").unwrap(); @@ -165,11 +494,15 @@ fn repeated_insertions_and_formatting_share_immutable_objects() { fn serialized_insertions_rebase_with_the_same_objects_and_preserve_remote_edits() { let source = onestore::create_section("rebase.one", "Original", "Author").unwrap(); let (sid, _, outline, paragraph, text) = targets(&source); - let intent = Insertion::paragraph(outline, Some(paragraph), "Inserted", "Author").unwrap(); + let intent = Insertion::paragraph(outline, Some(paragraph), "Inserted", "Author") + .unwrap() + .with_formatting(0..3, &[A::Bold(true), A::Color(Some([12, 34, 56]))]) + .unwrap(); let encoded = serde_json::to_vec(&intent).unwrap(); let restored: Insertion = serde_json::from_slice(&encoded).unwrap(); assert_eq!(intent.object(), restored.object()); assert_eq!(intent.text_object(), restored.text_object()); + assert_eq!(intent, restored); let original_preparation = PreparedEdit::insert(&source, sid, &intent).unwrap(); let remote = PreparedEdit::text(&source, sid, text, 0..0, "Remote ").unwrap(); let updated = PreparedEdit::insert(remote.as_bytes(), sid, &restored).unwrap(); @@ -188,6 +521,12 @@ fn serialized_insertions_rebase_with_the_same_objects_and_preserve_remote_edits( assert!( matches!(&view.nodes[&restored.text_object()].kind, Kind::RichText { text, .. } if text == "Inserted") ); + let runs = view.text_runs(restored.text_object()).unwrap(); + assert_eq!(runs[0].text, "Ins"); + assert_eq!(runs[0].format.bold, Some(true)); + assert_eq!(runs[0].format.color, Some(0x38220c)); + assert_eq!(runs[1].text, "erted"); + assert_ne!(runs[1].format.bold, Some(true)); assert!(PreparedEdit::insert(original_preparation.as_bytes(), sid, &restored).is_err()); assert!(PreparedEdit::insert(updated.as_bytes(), sid, &restored).is_err()); } @@ -196,8 +535,14 @@ fn serialized_insertions_rebase_with_the_same_objects_and_preserve_remote_edits( fn repositioning_preserves_intent_identity_and_kind() { let source = onestore::create_section("placement.one", "Original", "Author").unwrap(); let (sid, page, outline, paragraph, _) = targets(&source); - let p = Insertion::paragraph(outline, Some(paragraph), "Inserted", "Author").unwrap(); - let o = Insertion::outline(page, 144.0, 144.0, "Inserted", "Author").unwrap(); + let p = Insertion::paragraph(outline, Some(paragraph), "Inserted", "Author") + .unwrap() + .with_formatting(1..4, &[A::Italic(true)]) + .unwrap(); + let o = Insertion::outline(page, 144.0, 144.0, "Inserted", "Author") + .unwrap() + .with_formatting(0..8, &[A::FontSize(18.0)]) + .unwrap(); assert!(p.reposition_outline(page, 72.0, 72.0).is_err()); assert!(o.reposition_paragraph(outline, None).is_err()); assert!(p.reposition_paragraph(ExGuid::default(), None).is_err()); @@ -344,6 +689,11 @@ fn insertion_publication_faults_expose_only_complete_graphs_and_title_caches() { Insertion::paragraph(outline, Some(paragraph), "First 🦀", "New author").unwrap(), Insertion::outline(page, 0.0, 0.0, "First 🦀", "New author").unwrap(), ] { + let intent = intent + .with_formatting(0..3, &[A::Bold(true), A::FontSize(18.0)]) + .unwrap() + .with_formatting(6..8, &[A::Italic(true), A::Highlight(Some([255, 255, 0]))]) + .unwrap(); let edit = PreparedEdit::insert(source, sid, &intent).unwrap(); let before = current::current(source); let after = current::current(edit.as_bytes()); diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f898a806d40f6d2e347328f85e1cc012fd9f309c..edaf899d52a4762dbc9615ddefbf58c2baed74ef 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -77,3 +77,10 @@ path = "fuzz_targets/edit_text.rs" test = false doc = false bench = false + +[[bin]] +name = "insert" +path = "fuzz_targets/insert.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/insert.rs b/fuzz/fuzz_targets/insert.rs new file mode 100644 index 0000000000000000000000000000000000000000..ea3546f0ebe51c625a7f045288eabdd4b994abb7 --- /dev/null +++ b/fuzz/fuzz_targets/insert.rs @@ -0,0 +1,135 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use onestore::{ + CommitState, ExGuid, Insertion, PreparedEdit, RevisionIndex, Store, TextAttribute as A, + document::{Document, Kind}, +}; +use std::sync::LazyLock; + +#[path = "../../crates/onestore/tests/support/current.rs"] +mod current; +#[path = "../../crates/onestore/tests/support/disk.rs"] +mod disk; + +static SOURCE: LazyLock<(Vec, ExGuid, ExGuid, ExGuid)> = LazyLock::new(|| { + let bytes = include_bytes!( + "../../corpus/native/20260905-05/snapshots/03-format-unicode/notebook/synthetic.one" + ) + .to_vec(); + let store = Store::parse(&bytes).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let (space, page) = document.pages().unwrap()[0]; + let s = &document.spaces[&space]; + let view = &s.revisions[&s.contexts[&ExGuid::default()]]; + let outline = *view.nodes[&page] + .children + .iter() + .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. })) + .unwrap(); + (bytes, space, page, outline) +}); + +fuzz_target!(|input: &[u8]| { + let (source, space, page, outline) = &*SOURCE; + if let Ok(intent) = serde_json::from_slice::(input) + && let Ok(prepared) = PreparedEdit::insert(source, *space, &intent) + { + current::current(prepared.as_bytes()); + } + let mut persisted = source.clone(); + let mut caches = std::array::from_fn::<_, 12, _>(|_| source.clone()); + for step in input.chunks_exact(8).take(12) { + let actor = usize::from(step[0]) % caches.len(); + if step[1] % 4 == 0 { + caches[actor].clone_from(&persisted); + } + let source = &caches[actor]; + let text = + ["", "ab", "🦀e\u{301}東京\rEnd", "same style same style"][usize::from(step[2]) % 4]; + let offsets: Vec = std::iter::once(0) + .chain(text.chars().scan(0, |n, c| { + *n += c.len_utf16() as u32; + Some(*n) + })) + .collect(); + let first = usize::from(step[3]) % offsets.len(); + let second = usize::from(step[4]) % offsets.len(); + let (start, end) = (first.min(second), first.max(second)); + let plain = if step[1] & 1 == 0 { + Insertion::paragraph(*outline, None, text, "Insertion fuzz").unwrap() + } else { + Insertion::outline(*page, 72.0, 144.0, text, "Insertion fuzz").unwrap() + }; + let enabled = step[5] & 1 != 0; + let intent = if start != end || text.is_empty() { + plain + .with_formatting( + offsets[start]..offsets[end], + &[A::Bold(enabled), A::FontSize(18.0)], + ) + .unwrap() + } else { + plain + }; + let encoded = serde_json::to_vec(&intent).unwrap(); + let intent: Insertion = serde_json::from_slice(&encoded).unwrap(); + let edit = PreparedEdit::insert(source, *space, &intent).unwrap(); + let store = Store::parse(edit.as_bytes()).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let s = &document.spaces[space]; + let view = &s.revisions[&s.contexts[&ExGuid::default()]]; + let actual: Vec<_> = view + .text_runs(intent.text_object()) + .unwrap() + .into_iter() + .flat_map(|run| { + run.text.chars().map(move |c| { + ( + c, + run.format.bold.unwrap_or(false), + run.format.font_size.unwrap(), + ) + }) + }) + .collect(); + let expected: Vec<_> = text + .chars() + .enumerate() + .map(|(i, c)| { + if (start..end).contains(&i) { + (c, enabled, 18.0) + } else { + (c, false, 11.0) + } + }) + .collect(); + assert_eq!(actual, expected); + let before = current::current(&persisted); + let after = current::current(edit.as_bytes()); + let mut disk = disk::Disk { + visible: persisted.clone(), + durable: persisted.clone(), + operation: 0, + fail_at: (step[6] != 0).then_some(usize::from(step[6])), + write_limit: if step[7] & 1 == 0 { 17 } else { 4096 }, + random: u64::from(step[7]) + 1, + }; + let result = edit.commit(&mut disk); + let observed = current::current(&disk.durable); + match result { + Ok(()) => assert_eq!(observed, after), + Err(error) => { + assert!(observed == before || observed == after); + if error.state == CommitState::NotCommitted { + assert_eq!(observed, before); + } + if error.state == CommitState::Committed { + assert_eq!(observed, after); + } + } + } + persisted = disk.durable; + } +}); diff --git a/tools/native/formatted-insertion.ps1 b/tools/native/formatted-insertion.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..f767ae400bde912d46bc8f1d624088668c784fec --- /dev/null +++ b/tools/native/formatted-insertion.ps1 @@ -0,0 +1,53 @@ +param([Parameter(Mandatory=$true)][string]$Root, [string]$CloneHost = '') +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +& "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost +$app = New-Object -ComObject OneNote.Application +$notebook = '' +try { + $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$notebook, 0) + $section = '' + $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0) + $deadline = [DateTime]::UtcNow.AddSeconds(60) + do { + $hierarchy = '' + $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) + [xml]$tree = $hierarchy + $pages = @($tree.SelectNodes('//*[local-name()="Page"]')) + if ($pages.Count -eq 1) { break } + Start-Sleep -Milliseconds 250 + } while ([DateTime]::UtcNow -lt $deadline) + if ($pages.Count -ne 1) { throw 'Expected one fixture page.' } + $pageId = $pages[0].GetAttribute('ID') + foreach ($phase in @('control', 'edit')) { + $content = '' + $app.GetPageContent($pageId, [ref]$content, 1, 1) + [IO.File]::WriteAllText((Join-Path $Root ($phase + '-before.xml')), $content, [Text.Encoding]::UTF8) + [xml]$page = $content + $matches = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object { + $plain = [regex]::Replace($_.InnerText, '<[^>]*>', '') + $plain.StartsWith('Bold ') -or $plain.StartsWith('Typed ') + }) + if ($matches.Count -ne 1) { throw 'Expected one inserted rich paragraph.' } + $target = $matches[0] + if ($phase -eq 'edit') { $target.InnerText = $target.InnerText.Replace('Bold', 'Native Bold').Replace('Typed', 'Native Typed') } + $outline = $target + while ($outline.LocalName -ne 'Outline') { $outline = $outline.ParentNode } + $update = '' + foreach ($style in $page.SelectNodes('/*/*[local-name()="QuickStyleDef"]')) { $update += $style.OuterXml } + if ($outline.SelectSingleNode('.//*[local-name()="Table"]')) { + # Resubmitting measured outline size constrains native table auto-fit. + foreach ($size in @($outline.SelectNodes('*[local-name()="Size"]'))) { [void]$outline.RemoveChild($size) } + } + $update += $outline.OuterXml + '' + [IO.File]::WriteAllText((Join-Path $Root ($phase + '-update.xml')), $update, [Text.Encoding]::UTF8) + $app.UpdatePageContent($update, [DateTime]::MinValue, 1, $false) + } + $app.SyncHierarchy($notebook) +} finally { + if ($notebook) { $app.CloseNotebook($notebook, $false) } + [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) + $app = $null + [GC]::Collect() + [GC]::WaitForPendingFinalizers() +} diff --git a/tools/native/read.ps1 b/tools/native/read.ps1 index d61ba414eb59a498d2d686422b476d3263f77b48..476094ab1e0c47077988a6461db2080cf208642b 100644 --- a/tools/native/read.ps1 +++ b/tools/native/read.ps1 @@ -40,6 +40,7 @@ try { } $deadline = [DateTime]::UtcNow.AddSeconds(300) $previous = '' + $lastChange = '' $stableSince = [DateTime]::UtcNow $settled = $false do { @@ -62,6 +63,7 @@ try { } $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] })) if ($signature -ne $previous) { + $lastChange = $previous $previous = $signature $stableSince = [DateTime]::UtcNow } @@ -71,6 +73,7 @@ try { 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) @@ -79,7 +82,7 @@ try { $hierarchy = '' $app.GetHierarchy($notebookId, 4, [ref]$hierarchy, 1) [IO.File]::WriteAllText((Join-Path $output 'unsettled-hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8) - throw "Expected $ExpectedPages pages; OneNote returned $($pages.Count)." + throw "Expected $ExpectedPages stable pages; OneNote returned $($pages.Count), settled=$settled." } $index = 0 $payloads = @() diff --git a/tools/offline_document_history.py b/tools/offline_document_history.py index c1fd88a48abcb84d5cd91f3f33e1233d7ebea36e..23f0f8cc469866ebf280ff378958e2451d55bcc6 100644 --- a/tools/offline_document_history.py +++ b/tools/offline_document_history.py @@ -80,6 +80,11 @@ def document_history(logs, operations): assert formatted['range'] == [1, len(text.encode('utf-16-le')) // 2 - 2] assert formatted['attributes'] == [{'Bold': True}, {'FontSize': 18 + number % 9}, {'Color': [18, 52, 86]}] old = [(char, False, 11, 0xff000000) for char in text] + if insertion.get('formats'): + span, = insertion['formats'] + assert span['range'] == {'start': 0, 'end': len(text.encode('utf-16-le')) // 2}, 'Initial formatting range differs from workload' + assert span['attributes'] == [{'FontSize': 13.5}, {'Color': [68, 85, 102]}], 'Initial formatting attributes differ from workload' + old = [(char, False, 13.5, 0x665544) for char in text] new = [(char, True, 18 + number % 9, 0x563412) if 0 < i < len(text)-1 else old[i] for i, char in enumerate(text)] assert target not in documents, 'Two insertion intents share an object identity' created, changed = linked[inserted['id']], linked[formatted['id']] @@ -158,6 +163,6 @@ def verify_native(paragraphs, documents): if color == 0xff000000: assert native_color in ('automatic', None), 'Native automatic color changed' else: - assert ImageColor.getrgb(native_color) == (18, 52, 86), 'Native color differs from intent' + assert ImageColor.getrgb(native_color) == (color & 255, (color >> 8) & 255, (color >> 16) & 255), 'Native color differs from intent' checks += 3 return checks diff --git a/tools/test_formatted_insertion.py b/tools/test_formatted_insertion.py new file mode 100644 index 0000000000000000000000000000000000000000..15082a034f5568215370e9239dc530e56d2e50a1 --- /dev/null +++ b/tools/test_formatted_insertion.py @@ -0,0 +1,57 @@ +import json +from pathlib import Path +import runpy +import shutil +from tempfile import TemporaryDirectory +import unittest +import xml.etree.ElementTree as ET + +from native_format import native_characters +from native_xml import ns + +ROOT = Path(__file__).resolve().parent.parent +FIXTURE = ROOT / 'corpus/formatted-insertion' +compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] + + +class FormattedInsertionTest(unittest.TestCase): + def test_cold_native_content_and_formatting(self): + manifest = json.loads((FIXTURE / 'manifest.json').read_text()) + for case in manifest['cases']: + with self.subTest(case=case['name']), TemporaryDirectory() as temporary: + fixture = FIXTURE / case['name'] + native = Path(temporary) / 'read' + shutil.copytree(fixture / 'cold/read', native) + compare(fixture / 'candidate', native) + with TemporaryDirectory() as temporary: + fixture = FIXTURE / 'empty/typed-after-cold' + native = Path(temporary) / 'read' + shutil.copytree(fixture / 'read', native) + compare(fixture / 'candidate', native) + + def test_native_edits_preserve_the_observed_character_styles(self): + manifest = json.loads((FIXTURE / 'manifest.json').read_text()) + for case in manifest['cases']: + for variant, before_file, prefix in [('reverse', 'edit-before.xml', 'Native '), + ('ui', 'before-read/page-000.xml', 'UI ')]: + if variant + '_sha256' not in case: + continue + with self.subTest(case=case['name'], variant=variant), TemporaryDirectory() as temporary: + fixture = FIXTURE / case['name'] / variant + native = Path(temporary) / 'read' + shutil.copytree(fixture / 'read', native) + compare(fixture / 'notebook', native) + before = ET.parse(fixture / before_file).getroot() + after = ET.parse(native / 'page-000.xml').getroot() + expected = native_characters(before, before.findall('one:Outline', ns)) + changed = 0 + for index, paragraph in enumerate(expected): + if ''.join(char for char, _ in paragraph).startswith(('Bold ', 'Typed ')): + expected[index] = [(char, paragraph[0][1]) for char in prefix] + paragraph + changed += 1 + self.assertEqual(changed, 1) + self.assertEqual(native_characters(after, after.findall('one:Outline', ns)), expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/test_offline_document_history.py b/tools/test_offline_document_history.py index f25d7c486041cf40e7fbb93cda00eaff1ebe553e..4bea175efc829711e9cce266f0558ffde284c3f5 100644 --- a/tools/test_offline_document_history.py +++ b/tools/test_offline_document_history.py @@ -60,6 +60,34 @@ class DocumentHistoryTests(unittest.TestCase): events.remove(next(row for row in events if row['event'] == name)) with self.subTest(event=name), self.assertRaises(AssertionError): document_history(logs, 2) + def test_formatted_insertions_are_atomic_in_every_observed_snapshot(self): + for events in self.logs.values(): + for row in events: + insertion = row.get('insertion') + if insertion: + insertion['formats'] = [{'guid': list(uuid.uuid4().bytes_le), + 'range': {'start': 0, 'end': len(insertion['text'].encode('utf-16-le')) // 2}, + 'attributes': [{'FontSize': 13.5}, {'Color': [68, 85, 102]}]}] + for key in ('documents', 'document_changes'): + for document in (row.get(key) or {}).values(): + for run in document['runs']: + if run['size'] == 11: + run['size'] = 13.5 + run['color'] = 0x665544 + documents = document_history(self.logs, 2) + paragraphs = [[(char, {'bold': bold, 'font_size': size, + 'color': '#123456' if color == 0x563412 else '#445566'}) + for char, bold, size, color in row['new']] for row in documents.values()] + self.assertEqual(verify_native(paragraphs, documents), sum(len(row['new'])*3 for row in documents.values())) + baseline = copy.deepcopy(self.logs) + for event, key in [('remote_attempt', 'document_changes'), ('read', 'documents')]: + for field, value in [('size', 11), ('color', 0xff000000)]: + logs = copy.deepcopy(baseline) + row = next(row for row in logs['w0'] if row['event'] == event) + next(iter(row[key].values()))['runs'][0][field] = value + with self.subTest(event=event, field=field), self.assertRaises(AssertionError): + document_history(logs, 2) + def test_retired_format_receipt_requires_current_revision_and_exact_observed_effect(self): events = self.logs['w0'] attempt = next(row for row in events if row['event'] == 'remote_attempt' and row['revision'] == 'revision-3')