1#[path = "support/ops.rs"]
2mod ops;
3use onestore::{
4 ExGuid, RevisionIndex, Store,
5 document::{Document, Kind},
6 page::{Page, PageObject, Paragraph},
7};
8
9const SOURCE: &[u8] = include_bytes!("../../../corpus/paragraph-format/before/synthetic.one");
10
11fn page(bytes: &[u8]) -> (ExGuid, Page) {
12 let store = Store::parse(bytes).unwrap();
13 assert!(store.checksum_mismatches.is_empty());
14 let index = RevisionIndex::parse(&store).unwrap();
15 index.validate_current().unwrap();
16 let document = Document::parse(&index).unwrap();
17 document
18 .pages()
19 .unwrap()
20 .into_iter()
21 .find_map(|(space, _)| {
22 let page = Page::from_space(&document, space).unwrap();
23 (page.title == "Paragraph formatting").then_some((space, page))
24 })
25 .unwrap()
26}
27
28#[test]
29fn paragraph_properties_preserve_native_mixed_runs_and_shared_styles() {
30 let (space, before) = page(SOURCE);
31 let mut after = before.clone();
32 let mut count = 0;
33 for object in &mut after.objects {
34 if let PageObject::Outline(outline) = object {
35 for paragraph in &mut outline.paragraphs {
36 let text = paragraph.text_mut().unwrap();
37 let mut start = 0;
38 text.text = Paragraph::from_runs(text.text.spans().iter().map(|span| {
39 let value = text.text.text()[start..span.end].to_owned();
40 start = span.end;
41 let mut format = span.format.clone();
42 format.alignment = Some(1);
43 format.space_before = Some(0.0);
44 format.space_after = Some(2.0);
45 format.line_spacing = Some(16.0);
46 (value, format)
47 }));
48 count += 1;
49 }
50 }
51 }
52 assert_eq!(count, 6);
53 let prepared = ops::saved(SOURCE, space, &after).unwrap();
54 assert_eq!(page(prepared.as_slice()).1, after);
55 let before_store = Store::parse(SOURCE).unwrap();
56 let before_index = RevisionIndex::parse(&before_store).unwrap();
57 let before_doc = Document::parse(&before_index).unwrap();
58 let after_store = Store::parse(prepared.as_slice()).unwrap();
59 let after_index = RevisionIndex::parse(&after_store).unwrap();
60 let old = before_index.resolve_active(space).unwrap();
61 let new = after_index.resolve_active(space).unwrap();
62 for (id, node) in &before_doc.active(space).unwrap().nodes {
63 if matches!(node.kind, Kind::Style { .. }) {
64 assert_eq!(old.objects[id].data, new.objects[id].data);
65 }
66 }
67 if let Some(directory) = std::env::var_os("ONESTORE_PARAGRAPH_FORMAT_EXPORT") {
68 let directory = std::path::PathBuf::from(directory);
69 std::fs::create_dir(&directory).unwrap();
70 std::fs::write(directory.join("synthetic.one"), prepared.as_slice()).unwrap();
71 std::fs::write(
72 directory.join("Open Notebook.onetoc2"),
73 onestore::create_table_of_contents(
74 "Open Notebook.onetoc2",
75 &[("synthetic.one", after_store.header.file_id)],
76 )
77 .unwrap(),
78 )
79 .unwrap();
80 }
81}
82
83#[test]
84fn paragraph_formatting_rejects_invalid_values_and_conflicting_run_settings() {
85 let (space, before) = page(SOURCE);
86 let invalid: &[fn(&mut onestore::document::Format)] = &[
87 |format| format.alignment = Some(3),
88 |format| format.space_before = Some(-1.0),
89 |format| format.space_after = Some(f32::NAN),
90 |format| format.line_spacing = Some(f32::INFINITY),
91 |format| format.space_after = Some(1_000_100.0),
92 ];
93 for change in invalid {
94 let mut after = before.clone();
95 let text = after
96 .objects
97 .iter_mut()
98 .find_map(|object| match object {
99 PageObject::Outline(outline) => {
100 outline.paragraphs.iter_mut().find_map(|p| p.text_mut())
101 }
102 _ => None,
103 })
104 .unwrap();
105 let mut format = text.text.format_at(0).unwrap().clone();
106 change(&mut format);
107 text.text = Paragraph::new(text.text.text().to_owned(), format);
108 assert!(ops::saved(SOURCE, space, &after).is_err());
109 }
110 let mut after = before.clone();
111 let text = after
112 .objects
113 .iter_mut()
114 .find_map(|object| match object {
115 PageObject::Outline(outline) => {
116 outline.paragraphs.iter_mut().find_map(|p| p.text_mut())
117 }
118 _ => None,
119 })
120 .unwrap();
121 let mut left = text.text.format_at(0).unwrap().clone();
122 let mut right = left.clone();
123 left.alignment = Some(1);
124 right.alignment = Some(2);
125 text.text = Paragraph::from_runs([("Left".into(), left), ("Right".into(), right)]);
126 assert!(ops::saved(SOURCE, space, &after).is_err());
127}