1//! Tables as OneNote stores them: rows and cells are plain containers, a cell carries its
2//! indentation table and the flags every native cell has, the table its column widths,
3//! locks and border flag.
4
5use super::{
6 Values,
7 content::{NATIVE_INDENTS, measurement_bytes},
8};
9use crate::{
10 Error, ExGuid,
11 active::{ActivePage, Changes},
12 page::{Table, TableColumn},
13 write::PropertyObject,
14};
15use std::collections::{BTreeMap, BTreeSet};
16
17fn invalid(message: &'static str) -> Error {
18 Error { offset: 0, message }
19}
20
21/// Every row carries one cell per column and widths are usable.
22pub(crate) fn validate_table(table: &Table) -> Result<(), Error> {
23 if table.rows.is_empty() || table.columns.is_empty() {
24 return Err(invalid("A table needs at least one row and one column"));
25 }
26 if table
27 .columns
28 .iter()
29 .any(|c| !c.width.is_finite() || c.width < 36.0)
30 {
31 return Err(invalid("Table columns are at least 36 points wide"));
32 }
33 for row in &table.rows {
34 if row.cells.len() != table.columns.len() {
35 return Err(invalid("Every table row has one cell per column"));
36 }
37 for cell in &row.cells {
38 for paragraph in &cell.paragraphs {
39 if let crate::page::ParagraphContent::Table(nested) = &paragraph.content {
40 validate_table(nested)?;
41 }
42 }
43 }
44 }
45 Ok(())
46}
47
48/// A table's rows and cells by stored identity, in order, and what new ones carry.
49pub(crate) struct Structure<'t> {
50 pub rows: Vec<(ExGuid, Vec<ExGuid>)>,
51 pub new_rows: BTreeSet<ExGuid>,
52 /// The indentation table and shading of each new cell.
53 pub new_cells: BTreeMap<ExGuid, (&'t [f32], Option<u32>)>,
54 pub columns: &'t [TableColumn],
55 pub borders: Option<bool>,
56}
57
58/// The indentation table a new cell takes: its own, else the table's first cell's, else
59/// the one OneNote gives new cells.
60pub(crate) fn cell_indents<'t>(own: &'t [f32], template: Option<&'t [f32]>) -> &'t [f32] {
61 [own, template.unwrap_or_default(), &NATIVE_INDENTS]
62 .into_iter()
63 .find(|indents| !indents.is_empty())
64 .unwrap()
65}
66
67/// Writes table `table` as `structure`; with `holder`, creates it as that paragraph's content.
68pub(crate) fn table_changes(
69 active: &ActivePage<'_>,
70 table: ExGuid,
71 holder: Option<ExGuid>,
72 structure: &Structure<'_>,
73) -> Result<Changes, Error> {
74 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
75 let mut created: Vec<(ExGuid, u32, Values)> = Vec::new();
76 for (row, cells) in &structure.rows {
77 for cell in cells {
78 if let Some((indents, shading)) = structure.new_cells.get(cell) {
79 let mut values: Values = vec![
80 (0x14001d7a, modified.to_vec()),
81 (0x0c001c13, vec![0]),
82 (0x0c001c03, vec![1]),
83 (0x88001c91, Vec::new()),
84 (0x1c001c12, measurement_bytes(indents, 4)?),
85 ];
86 if let Some(shading) = shading {
87 values.push((0x14001e26, shading.to_le_bytes().to_vec()));
88 }
89 created.push((*cell, 0x60024, values));
90 }
91 }
92 if structure.new_rows.contains(row) {
93 created.push((*row, 0x60023, vec![(0x14001d7a, modified.to_vec())]));
94 }
95 }
96 let columns = structure.columns;
97 let mut table_values: Values = vec![
98 (0x14001d7a, modified.to_vec()),
99 (
100 0x14001d57,
101 (structure.rows.len() as u32).to_le_bytes().to_vec(),
102 ),
103 (0x14001d58, (columns.len() as u32).to_le_bytes().to_vec()),
104 (
105 0x1c001d66,
106 measurement_bytes(&columns.iter().map(|c| c.width).collect::<Vec<_>>(), 1)?,
107 ),
108 ];
109 let mut locks = vec![columns.len() as u8];
110 locks.extend(vec![0u8; columns.len().div_ceil(8)]);
111 for (i, column) in columns.iter().enumerate() {
112 if column.locked {
113 locks[1 + i / 8] |= 1 << (i % 8);
114 }
115 }
116 table_values.push((0x1c001d7d, locks));
117 table_values.push((
118 0x08001d5e | (u32::from(structure.borders.unwrap_or(true)) << 31),
119 Vec::new(),
120 ));
121 if holder.is_some() {
122 table_values.push((0x14001c3e, 1u32.to_le_bytes().to_vec()));
123 table_values.push((0x14001c84, 1u32.to_le_bytes().to_vec()));
124 created.push((table, 0x60022, Vec::new()));
125 }
126 let raw = &active.live.revision;
127 let mut changed: Changes = BTreeMap::new();
128 for (id, jcid, values) in &created {
129 let mut node = PropertyObject {
130 jcid: *jcid,
131 bytes: crate::create::properties(values)?,
132 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
133 };
134 node.reference(*id)?;
135 changed.insert(*id, node);
136 }
137 let mut row_refs = Vec::new();
138 for (row_id, cells) in &structure.rows {
139 let mut row = match changed.remove(row_id) {
140 Some(row) => row,
141 None => PropertyObject::from_object(&raw.objects[row_id])?,
142 };
143 let mut cell_refs = Vec::new();
144 for cell in cells {
145 cell_refs.extend_from_slice(&row.reference(*cell)?);
146 }
147 row.set(&[(0x24001c20, &cell_refs), (0x14001d7a, &modified)])?;
148 changed.insert(*row_id, row);
149 }
150 let mut table_object = match changed.remove(&table) {
151 Some(object) => object,
152 None => PropertyObject::from_object(&raw.objects[&table])?,
153 };
154 for (row_id, _) in &structure.rows {
155 row_refs.extend_from_slice(&table_object.reference(*row_id)?);
156 }
157 table_object.remove(&[0x1c001d7d, 0x08001d5e])?;
158 table_object.set(
159 &table_values
160 .iter()
161 .map(|(id, bytes)| (*id, bytes.as_slice()))
162 .collect::<Vec<_>>(),
163 )?;
164 table_object.set(&[(0x24001c20, &row_refs)])?;
165 changed.insert(table, table_object);
166 if let Some(holder) = holder {
167 let mut object = PropertyObject::from_object(&raw.objects[&holder])?;
168 let reference = object.reference(table)?;
169 object.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?;
170 changed.insert(holder, object);
171 }
172 Ok(changed)
173}
174
175/// Shading (the documented `CellShadingColor`, which OneNote 2010 stores but neither
176/// renders nor accepts through its COM schema) and indents change in place on a cell.
177pub(crate) fn cell_changes(
178 active: &ActivePage<'_>,
179 cell: ExGuid,
180 stored: (Option<u32>, &[f32]),
181 shading: Option<u32>,
182 indents: &[f32],
183) -> Result<Changes, Error> {
184 let mut values: Values = vec![(
185 0x14001d7a,
186 crate::create::current_timestamps()?
187 .0
188 .to_le_bytes()
189 .to_vec(),
190 )];
191 let mut removed = Vec::new();
192 if shading != stored.0 {
193 match shading {
194 Some(shading) => values.push((0x14001e26, shading.to_le_bytes().to_vec())),
195 None => removed.push(0x14001e26),
196 }
197 }
198 if indents != stored.1 {
199 if indents.is_empty() {
200 return Err(invalid("A table cell needs an indentation table"));
201 }
202 values.push((0x1c001c12, measurement_bytes(indents, 4)?));
203 }
204 let raw = &active.live.revision;
205 let mut object = PropertyObject::from_object(&raw.objects[&cell])?;
206 object.remove(&removed)?;
207 let values: Vec<(u32, &[u8])> = values
208 .iter()
209 .map(|(id, bytes)| (*id, bytes.as_slice()))
210 .collect();
211 object.set(&values)?;
212 Ok(BTreeMap::from([(cell, object)]))
213}