| 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 | |
| 5 | use super::{ |
| 6 | Values, |
| 7 | content::{NATIVE_INDENTS, measurement_bytes}, |
| 8 | }; |
| 9 | use crate::{ |
| 10 | Error, ExGuid, |
| 11 | active::{ActivePage, Changes}, |
| 12 | page::{Table, TableColumn}, |
| 13 | write::PropertyObject, |
| 14 | }; |
| 15 | use std::collections::{BTreeMap, BTreeSet}; |
| 16 | |
| 17 | fn invalid(message: &'static str) -> Error { |
| 18 | Error { offset: 0, message } |
| 19 | } |
| 20 | |
| 21 | /// Every row carries one cell per column and widths are usable. |
| 22 | pub(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. |
| 49 | pub(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. |
| 60 | pub(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. |
| 68 | pub(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. |
| 177 | pub(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 | } |