1#!/usr/bin/env python3
2"""Build synthetic OneNote pages for tools/native_runner.py --author tools/native/pages.ps1."""
3import argparse
4import html
5import json
6import math
7from pathlib import Path
8import xml.etree.ElementTree as ET
9
10NS = 'http://schemas.microsoft.com/office/onenote/2010/onenote'
11ET.register_namespace('one', NS)
12
13
14def build(cases):
15 pages = []
16 seen = set()
17 for case in cases:
18 if case['id'] in seen:
19 raise ValueError('Duplicate case identity')
20 seen.add(case['id'])
21 if not math.isfinite(case['width']) or case['width'] <= 0 or not case['runs']:
22 raise ValueError('Expected positive width and at least one run')
23 page = ET.Element(f'{{{NS}}}Page', {'name': case['id']})
24 title = ET.SubElement(page, f'{{{NS}}}Title')
25 title_oe = ET.SubElement(title, f'{{{NS}}}OE')
26 ET.SubElement(title_oe, f'{{{NS}}}T').text = case['id']
27 outline = ET.SubElement(page, f'{{{NS}}}Outline')
28 ET.SubElement(outline, f'{{{NS}}}Position', {'x': '36', 'y': '90', 'z': '0'})
29 ET.SubElement(outline, f'{{{NS}}}Size', {'width': str(case['width']), 'height': '20', 'isSetByUser': 'true'})
30 children = ET.SubElement(outline, f'{{{NS}}}OEChildren')
31 fragments = []
32 styles = []
33 for run in case['runs']:
34 if not math.isfinite(run['size']) or run['size'] <= 0:
35 raise ValueError('Expected positive font size')
36 style = (f"font-family:{run['font']};font-size:{run['size']}pt;"
37 f"font-weight:{'bold' if run['bold'] else 'normal'};"
38 f"font-style:{'italic' if run['italic'] else 'normal'}")
39 styles.append(style)
40 fragments.append(f'<span style="{html.escape(style, quote=True)}">{html.escape(run["text"])}</span>')
41 oe = ET.SubElement(children, f'{{{NS}}}OE', {'style': styles[0]})
42 ET.SubElement(oe, f'{{{NS}}}T').text = ''.join(fragments)
43 pages.append({'section': 'Layout.one', 'xml': ET.tostring(page, encoding='unicode')})
44 return {'sections': ['Layout.one'], 'pages': pages}
45
46
47if __name__ == '__main__':
48 parser = argparse.ArgumentParser(description=__doc__)
49 parser.add_argument('cases', type=Path)
50 parser.add_argument('destination', type=Path)
51 args = parser.parse_args()
52 fixture = build(json.loads(args.cases.read_text()))
53 args.destination.mkdir(parents=True, exist_ok=False)
54 (args.destination / 'fixture.json').write_text(json.dumps(fixture, ensure_ascii=False, indent=2) + '\n')