| 1 | from pathlib import Path |
| 2 | import runpy |
| 3 | import shutil |
| 4 | from tempfile import TemporaryDirectory |
| 5 | import unittest |
| 6 | import xml.etree.ElementTree as ET |
| 7 | |
| 8 | from native_xml import ns |
| 9 | |
| 10 | ROOT = Path(__file__).resolve().parent.parent |
| 11 | FIXTURE = ROOT / 'corpus/ink-tools' |
| 12 | compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] |
| 13 | |
| 14 | |
| 15 | def cold_pages(variant): |
| 16 | """The cold read of a Rust candidate, after checking it holds what the candidate stored.""" |
| 17 | with TemporaryDirectory() as temporary: |
| 18 | read = Path(temporary) / 'read' |
| 19 | shutil.copytree(FIXTURE / variant / 'cold/read', read) |
| 20 | compare(FIXTURE / variant / 'candidate', read) |
| 21 | return pages(FIXTURE / variant / 'cold/read') |
| 22 | |
| 23 | |
| 24 | def pages(read): |
| 25 | return {page.get('name'): page for page in (ET.parse(path).getroot() for path in sorted(read.glob('page-*.xml')))} |
| 26 | |
| 27 | |
| 28 | def drawings(page): |
| 29 | return page.findall('one:InkDrawing', ns) |
| 30 | |
| 31 | |
| 32 | def place(drawing): |
| 33 | position, size = drawing.find('one:Position', ns), drawing.find('one:Size', ns) |
| 34 | return [float(position.get('x')), float(position.get('y')), float(size.get('width')), float(size.get('height'))] |
| 35 | |
| 36 | |
| 37 | class InkToolsTest(unittest.TestCase): |
| 38 | def assertPlaced(self, drawing, expected, delta=0.06): |
| 39 | for actual, wanted in zip(place(drawing), expected, strict=True): |
| 40 | self.assertAlmostEqual(actual, wanted, delta=delta) |
| 41 | |
| 42 | def test_onenote_keeps_each_stroke_and_shape_a_drawing_of_its_own(self): |
| 43 | read = pages(FIXTURE / 'native-ui/read') |
| 44 | self.assertEqual(len(drawings(read['Gallery'])), 14) |
| 45 | shapes = [d.find('one:ShapeInfo', ns) for d in drawings(read['Pens'])] |
| 46 | self.assertEqual(sorted(shape.get('isLine') for shape in shapes if shape is not None), ['false', 'false', 'true', 'true']) |
| 47 | self.assertTrue((FIXTURE / 'native-ui/read/page-001.png').exists()) |
| 48 | |
| 49 | def test_snowbound_pens_highlighters_and_shapes_open_in_onenote(self): |
| 50 | page, = cold_pages('drawn').values() |
| 51 | found = drawings(page) |
| 52 | self.assertEqual(len(found), 6) |
| 53 | shapes = {d.get('objectID'): d.find('one:ShapeInfo', ns) for d in found} |
| 54 | lines = [s for s in shapes.values() if s is not None and s.get('isLine') == 'true'] |
| 55 | closed = [s for s in shapes.values() if s is not None and s.get('isLine') == 'false'] |
| 56 | self.assertEqual((len(lines), len(closed)), (2, 2)) |
| 57 | rectangle = next(s for s in closed if len(s.findall('one:AnchorPoint', ns)) == 8) |
| 58 | corners = [(float(a.get('x')), float(a.get('y'))) for a in rectangle.findall('one:AnchorPoint', ns)] |
| 59 | self.assertAlmostEqual(corners[0][0], 252.0, delta=0.01) |
| 60 | self.assertAlmostEqual(corners[4][1], 194.4, delta=0.01) |
| 61 | # The highlighter's stroke spans its points, one HIMETRIC unit larger, as OneNote reports ink. |
| 62 | highlighter = next(d for d in found if abs(place(d)[3]) < 0.1) |
| 63 | self.assertPlaced(highlighter, [60.0, 190.0, 160.0, 0.03]) |
| 64 | self.assertTrue((FIXTURE / 'drawn/cold/read/page-000.png').exists()) |
| 65 | |
| 66 | def test_onenote_drawings_keep_their_ink_after_snowbound_erases_moves_and_draws(self): |
| 67 | # Object identities differ between clones' reads, so drawings match by where they lie. |
| 68 | before = [place(d) for d in drawings(pages(FIXTURE / 'native-ui/read')['Pens'])] |
| 69 | after = [place(d) for d in drawings(cold_pages('edited')['Pens'])] |
| 70 | self.assertEqual(len(after), len(before) - 1 + 6) |
| 71 | near = lambda a, b: all(abs(x - y) < 0.001 for x, y in zip(a, b)) |
| 72 | zigzag = next(p for p in before if abs(p[2] - 90.03) < 0.01 and abs(p[3] - 22.51) < 0.01) |
| 73 | line = next(p for p in before if p[2] < 0.1 and abs(p[3] - 60.04) < 0.01) |
| 74 | self.assertFalse(any(near(p, zigzag) for p in after)) |
| 75 | self.assertTrue(any(near(p, [line[0] + 36.0, line[1] + 18.0, line[2], line[3]]) for p in after)) |
| 76 | for kept in before: |
| 77 | if kept not in (zigzag, line): |
| 78 | self.assertTrue(any(near(p, kept) for p in after), kept) |
| 79 | |
| 80 | |
| 81 | if __name__ == '__main__': |
| 82 | unittest.main() |