1use crate::{
2 Error, ExGuid, Object, ObjectData,
3 active::{ActivePage, Changes},
4 create::{current_timestamps, default_text_style, properties, string},
5 document::Kind,
6 op::content::{NATIVE_INDENTS, measurement_bytes},
7 write::{PropertyObject, fresh_guid},
8};
9use std::{
10 collections::{BTreeMap, BTreeSet},
11 sync::Arc,
12};
13
14fn invalid(message: &'static str) -> Error {
15 Error { offset: 0, message }
16}
17
18#[derive(Debug, Clone, PartialEq)]
19enum Placement {
20 Paragraph { before: Option<ExGuid> },
21 Outline { x: f32, y: f32 },
22}
23
24/// A paragraph or outline insertion with its creation time; `changes_as` names the new
25/// objects.
26#[derive(Debug, Clone, PartialEq)]
27pub(crate) struct Insertion {
28 guid: [u8; 16],
29 parent: ExGuid,
30 placement: Placement,
31 text: String,
32 author: String,
33 created: u32,
34}
35
36impl Insertion {
37 /// Inserts before a direct child, or appends when `before` is None.
38 /// The parent must be an editable outline, paragraph, outline group or table cell.
39 /// Carriage returns represent soft line breaks; line feeds and embedded-field markers are rejected.
40 pub(crate) fn paragraph(
41 parent: ExGuid,
42 before: Option<ExGuid>,
43 text: &str,
44 author: &str,
45 ) -> Result<Self, Error> {
46 Self::new(parent, Placement::Paragraph { before }, text, author)
47 }
48
49 /// Adds an outline to an editable page at coordinates measured in points.
50 pub(crate) fn outline(
51 page: ExGuid,
52 x: f32,
53 y: f32,
54 text: &str,
55 author: &str,
56 ) -> Result<Self, Error> {
57 Self::new(page, Placement::Outline { x, y }, text, author)
58 }
59
60 fn new(parent: ExGuid, placement: Placement, text: &str, author: &str) -> Result<Self, Error> {
61 let intent = Self {
62 guid: fresh_guid()?,
63 parent,
64 placement,
65 text: text.to_owned(),
66 author: author.to_owned(),
67 created: current_timestamps()?.0,
68 };
69 intent.validate()?;
70 Ok(intent)
71 }
72
73 fn validate(&self) -> Result<(), Error> {
74 if self.guid == [0; 16]
75 || self.parent.guid == [0; 16]
76 || self.text.contains(['\0', '\n', '\u{fffc}'])
77 || self.author.contains('\0')
78 {
79 return Err(invalid(
80 "Use an editable parent, ordinary paragraph text and a valid author",
81 ));
82 }
83 if let Placement::Outline { x, y } = self.placement
84 && (!x.is_finite() || !y.is_finite())
85 {
86 return Err(invalid("Outline coordinates must be finite"));
87 }
88 Ok(())
89 }
90
91 /// The objects creating the outline or paragraph `object`, the paragraph `paragraph` and
92 /// its text `text` under those identities.
93 pub(crate) fn changes_as(
94 &self,
95 active: &ActivePage<'_>,
96 object: ExGuid,
97 paragraph: ExGuid,
98 text: ExGuid,
99 ) -> Result<Changes, Error> {
100 let [page] = active.pages.as_slice() else {
101 return Err(invalid("Insertion requires a single active page"));
102 };
103 let view = &active.view;
104 let parents = active.editable_parents(self.parent)?;
105 let parent = &view.nodes[&self.parent];
106 let position = match self.placement {
107 Placement::Paragraph { before } => {
108 if !matches!(
109 parent.kind,
110 Kind::Outline { .. }
111 | Kind::Paragraph { .. }
112 | Kind::OutlineGroup
113 | Kind::Cell { .. }
114 ) {
115 return Err(invalid(
116 "Select an outline, paragraph, outline group or table cell",
117 ));
118 }
119 if let Some(id) = before {
120 parent
121 .children
122 .iter()
123 .position(|child| *child == id)
124 .ok_or_else(|| {
125 invalid("The insertion anchor is no longer a direct child")
126 })?
127 } else {
128 parent.children.len()
129 }
130 }
131 Placement::Outline { .. } => {
132 if self.parent != *page || !matches!(parent.kind, Kind::Page { .. }) {
133 return Err(invalid("Select an active page for the new outline"));
134 }
135 parent.children.len()
136 }
137 };
138 let mut ancestors = BTreeSet::new();
139 let mut pending = vec![self.parent];
140 while let Some(id) = pending.pop() {
141 if !ancestors.insert(id) {
142 continue;
143 }
144 if matches!(view.nodes[&id].kind, Kind::Title) {
145 return Err(invalid(
146 "Title containers do not accept ordinary paragraphs",
147 ));
148 }
149 pending.extend(parents.get(&id).into_iter().flatten().copied());
150 }
151 let modified = current_timestamps()?.0.to_le_bytes();
152 let (author, default) = (
153 ExGuid {
154 guid: self.guid,
155 n: 4,
156 },
157 ExGuid {
158 guid: self.guid,
159 n: 5,
160 },
161 );
162 let mut table = BTreeMap::from([(0, self.guid)]);
163 for id in [object, paragraph, text] {
164 if !table.values().any(|guid| *guid == id.guid) {
165 table.insert(table.len() as u32, id.guid);
166 }
167 }
168 let table = Arc::new(table);
169 let reference =
170 |id: ExGuid| crate::write::compact(id, &table).map(|compact| compact.to_vec());
171 let mut new = BTreeMap::new();
172 for (id, jcid, values) in [
173 (
174 paragraph,
175 0x6000d,
176 vec![
177 (0x14001d7a, modified.to_vec()),
178 (0x14001d09, self.created.to_le_bytes().to_vec()),
179 (0x0c001c03, vec![1]),
180 (0x24001c1f, reference(text)?),
181 (0x20001d78, reference(author)?),
182 (0x20001d79, reference(author)?),
183 ],
184 ),
185 (
186 text,
187 0x6000e,
188 vec![
189 (0x14001d7a, modified.to_vec()),
190 (0x1c001c22, string(&self.text)),
191 (0x24001e13, reference(default)?),
192 (0x10001cfe, 0x409_u16.to_le_bytes().to_vec()),
193 ],
194 ),
195 (
196 author,
197 0x120001,
198 crate::create::author_properties(&self.author),
199 ),
200 (default, 0x12004d, default_text_style()),
201 ] {
202 new.insert(
203 id,
204 PropertyObject {
205 jcid,
206 bytes: properties(&values)?,
207 global_ids: Arc::clone(&table),
208 },
209 );
210 }
211 if let Placement::Outline { x, y } = self.placement {
212 let children = reference(paragraph)?;
213 new.insert(
214 object,
215 PropertyObject {
216 jcid: 0x6000c,
217 global_ids: table,
218 bytes: properties(&[
219 (0x14001d7a, modified.to_vec()),
220 (0x24001c20, children),
221 (0x0c001c03, vec![1]),
222 (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?),
223 (0x14001c14, (x / 36.0).to_le_bytes().to_vec()),
224 (0x14001c15, (y / 36.0).to_le_bytes().to_vec()),
225 (0x14001c1b, 13_f32.to_le_bytes().to_vec()),
226 (0x14001c1c, 0.6_f32.to_le_bytes().to_vec()),
227 ])?,
228 },
229 );
230 }
231 let raw = &active.live.revision;
232 // Drop the overlay before moving the property bytes it borrows.
233 let title = {
234 let mut overlay = BTreeMap::new();
235 let mut parent = view.nodes[&self.parent].clone();
236 parent.children.insert(position, object);
237 overlay.insert(self.parent, parent);
238 for (id, object) in &new {
239 overlay.insert(
240 *id,
241 active.element(&Object {
242 jcid: object.jcid,
243 reference_count: 0,
244 data: ObjectData::Properties(&object.bytes),
245 global_ids: Arc::clone(&object.global_ids),
246 })?,
247 );
248 }
249 active.title(&overlay, None)?
250 };
251 let mut changed = new;
252 for id in &ancestors {
253 let mut object = PropertyObject::from_object(&raw.objects[id])?;
254 object.set(&[(0x14001d7a, &modified)])?;
255 changed.insert(*id, object);
256 }
257 let parent = changed.get_mut(&self.parent).unwrap();
258 let properties = crate::PropertySets::parse(&parent.bytes)?;
259 let existing = properties.sets[0].iter().find(|p| p.id == 0x24001c20);
260 let mut ids = match existing.map(|p| &p.value) {
261 Some(crate::Value::References { compact_ids, .. }) => compact_ids.to_vec(),
262 None => Vec::new(),
263 _ => return Err(invalid("The parent has an invalid child list")),
264 };
265 let child = parent.reference(object)?;
266 if position > ids.len() / 4 {
267 return Err(invalid("The parent has an invalid child list"));
268 }
269 ids.splice(position * 4..position * 4, child);
270 parent.set(&[(0x24001c20, &ids)])?;
271 if matches!(self.placement, Placement::Paragraph { .. }) {
272 let properties = crate::PropertySets::parse(&parent.bytes)?;
273 if !properties.sets[0].iter().any(|p| p.id == 0x0c001c03) {
274 parent.set(&[(0x0c001c03, &[1])])?;
275 }
276 }
277 if let Some((page, automatic, title)) = title {
278 let metadata = raw
279 .roots
280 .get(&2)
281 .ok_or_else(|| invalid("Page title metadata is unavailable"))?;
282 if ![0x20030, 0x20038].contains(&raw.objects[metadata].jcid) {
283 return Err(invalid("Page title metadata is unavailable"));
284 }
285 let title = string(&title);
286 let mut metadata_object = PropertyObject::from_object(&raw.objects[metadata])?;
287 metadata_object.set(&[(0x1c001cf3, &title)])?;
288 changed.insert(*metadata, metadata_object);
289 changed
290 .get_mut(&page)
291 .unwrap()
292 .set(&[(0x1c001d3c, if automatic { &title } else { &[0, 0] })])?;
293 }
294 Ok(changed)
295 }
296}