1#![no_main]
2use canvas::{date::PageDate, document::{TextDocument, TextPosition}, editor::{CanvasEditor, TextOutline}, layout::TextEngine};
3use draw::edit::{Movement, SelectionUnit};
4use onestore::page::text::{Paragraph};
5use libfuzzer_sys::fuzz_target;
6use onestore::document::Format;
7use std::{cell::RefCell, collections::BTreeMap};
8
9thread_local! {
10 static ENGINE: RefCell<TextEngine> = RefCell::new(TextEngine::default());
11}
12
13fuzz_target!(|input: &[u8]| {
14 ENGINE.with_borrow_mut(|engine| {
15 if input.first().is_some_and(|byte| byte & 32 != 0) {
16 let document = TextDocument::new(vec![Paragraph::new(
17 "ALPHA".into(),
18 Format {
19 font_size: (input[0] & 64 != 0).then_some(130.0),
20 ..Default::default()
21 },
22 )])
23 .unwrap();
24 let mut editor = CanvasEditor::new(engine, document, 180.0).unwrap();
25 for step in input.chunks_exact(4).take(24) {
26 let positions = editor
27 .active_outline()
28 .document()
29 .paragraphs()
30 .enumerate()
31 .flat_map(|(paragraph, text)| {
32 text.text()
33 .char_indices()
34 .map(move |(byte, _)| TextPosition {
35 paragraph,
36 offset: text.utf16_offset(byte).unwrap(),
37 })
38 .chain(std::iter::once(TextPosition {
39 paragraph,
40 offset: text.utf16_offset(text.text().len()).unwrap(),
41 }))
42 })
43 .collect::<Vec<_>>();
44 let focus = positions[usize::from(step[0]) % positions.len()];
45 let anchor = if step[3] & 16 == 0 {
46 focus
47 } else {
48 positions[usize::from(step[3]) % positions.len()]
49 };
50 editor.select([anchor, focus].into()).unwrap();
51 let before = editor.active_outline().document().clone();
52 let stored = editor
53 .outlines()
54 .iter()
55 .map(|outline| (outline.id, outline.document().clone()))
56 .collect::<Vec<_>>();
57 let selection = editor.selection();
58 let result = match step[1] % 10 {
59 0 => editor.tab(engine, false),
60 1 => editor.tab(engine, true),
61 2 => editor.enter(engine, false),
62 3 => editor.enter(engine, true),
63 4 => editor.insert(
64 engine,
65 ["x", "🧊", "left\nright", "العربية"][usize::from(step[2]) % 4],
66 ),
67 5 => editor.delete(engine, step[2] & 1 == 0).map(|_| ()),
68 6 => editor.indent(engine, step[2] & 1 == 0).map(|_| ()),
69 7 => editor
70 .move_selection(
71 engine,
72 [
73 Movement::Left,
74 Movement::Right,
75 Movement::Up,
76 Movement::Down,
77 ][usize::from(step[2]) % 4],
78 step[3] & 32 != 0,
79 )
80 .inspect_err(|error| {
81 assert!(!matches!(
82 error,
83 canvas::editor::EditorError::Edit(
84 onestore::page::text::EditError::InvalidStructure
85 )
86 ));
87 }),
88 8 => {
89 let unit = [
90 SelectionUnit::Grapheme,
91 SelectionUnit::Word,
92 SelectionUnit::Paragraph,
93 ][usize::from(step[2]) % 3];
94 editor
95 .selection_at(
96 f32::from(step[2]) - 32.0,
97 f32::from(step[3]) - 32.0,
98 unit,
99 )
100 .and_then(|selection| editor.select(selection))
101 .map_err(Into::into)
102 }
103 _ => editor.compose(engine, "🧊\n中".into(), 4..4).and_then(|_| {
104 if step[3] & 64 != 0 {
105 editor.cancel_composition(engine).map(|_| ())
106 } else {
107 editor.commit_text(engine, "done".into())
108 }
109 }),
110 };
111 if result.is_err() {
112 assert_eq!(editor.active_outline().document(), &before);
113 assert_eq!(editor.selection(), selection);
114 assert_eq!(
115 editor
116 .outlines()
117 .iter()
118 .map(|outline| (outline.id, outline.document().clone()))
119 .collect::<Vec<_>>(),
120 stored
121 );
122 continue;
123 }
124 let after = editor.active_outline().document().clone();
125 let after_selection = editor.selection();
126 let committed = editor
127 .outlines()
128 .iter()
129 .map(|outline| (outline.id, outline.document().clone()))
130 .collect::<Vec<_>>();
131 if committed != stored {
132 assert!(editor.undo(engine).unwrap());
133 assert_eq!(
134 editor
135 .outlines()
136 .iter()
137 .map(|outline| (outline.id, outline.document().clone()))
138 .collect::<Vec<_>>(),
139 stored
140 );
141 assert_eq!(editor.active_outline().document(), &before);
142 assert_eq!(editor.selection(), selection);
143 assert!(editor.redo(engine).unwrap());
144 assert_eq!(
145 editor
146 .outlines()
147 .iter()
148 .map(|outline| (outline.id, outline.document().clone()))
149 .collect::<Vec<_>>(),
150 committed
151 );
152 assert_eq!(editor.active_outline().document(), &after);
153 assert_eq!(editor.selection(), after_selection);
154 }
155 }
156 return;
157 }
158 let width = [1.0, 10.0, 72.0, 240.0][usize::from(input.first().copied().unwrap_or(0)) % 4];
159 let document = TextDocument::new(
160 ["office e\u{301} 👩🏽‍💻", "العربية 日本"]
161 .map(|text| Paragraph::new(text.into(), Format::default()))
162 .into(),
163 )
164 .unwrap();
165 let position = [
166 [0.0, 179.9, 180.0, 270.0][usize::from(input.first().copied().unwrap_or(0) >> 2) % 4],
167 158.4,
168 ];
169 let mut outline = TextOutline::new(engine, document, width, position).unwrap();
170 if input.first().is_some_and(|byte| byte & 128 != 0) {
171 let mut source = outline.snapshot();
172 source.title = true;
173 source.min_width = Some(162.0);
174 source.layout.max_width = None;
175 source.layout.width_set_by_user = None;
176 outline = TextOutline::from_outline(engine, &source, &BTreeMap::new()).unwrap();
177 }
178 let date = if input.first().is_some_and(|byte| byte & 64 != 0) {
179 let document = TextDocument::new(
180 ["Monday", "6:14 AM"]
181 .map(|text| Paragraph::new(text.into(), Format::default()))
182 .into(),
183 )
184 .unwrap();
185 let source = TextOutline::new(engine, document, 468.0, [0.0; 2])
186 .unwrap()
187 .snapshot();
188 Some(PageDate::new(100, source, engine, &BTreeMap::new()).unwrap())
189 } else {
190 None
191 };
192 let mut editor = if outline.title {
193 let mut fields = vec![outline.snapshot()];
194 let date_id = date.as_ref().map(|date| date.source().id);
195 if let Some(date) = &date {
196 fields.push(date.source().clone());
197 }
198 let mut objects = vec![onestore::page::PageObject::Title(onestore::page::Title {
199 id: onestore::ExGuid::default(),
200 layout: Default::default(),
201 date: date_id,
202 outlines: fields,
203 })];
204 for (index, (x, y, background)) in [
205 (120.0, 70.0, false),
206 (600.0, 180.0, false),
207 (120.0, 70.0, true),
208 ]
209 .into_iter()
210 .enumerate()
211 {
212 let mut image =
213 canvas::editor::picture(b"deferred decoding".to_vec(), [72.0, 40.0]).unwrap();
214 image.id = onestore::ExGuid {
215 n: index.try_into().unwrap(),
216 ..Default::default()
217 };
218 [image.layout.x, image.layout.y] = [Some(x), Some(position[1] + y)];
219 image.background = background;
220 objects.push(onestore::page::PageObject::Image(image));
221 }
222 // An empty editor's page is a page with every property at its default.
223 let blank = TextDocument::new(vec![Paragraph::new(String::new(), Format::default())]);
224 let mut page = CanvasEditor::new(engine, blank.unwrap(), 468.0)
225 .unwrap()
226 .page()
227 .unwrap();
228 page.created = date.map(|date| date.timestamp());
229 page.objects = objects;
230 CanvasEditor::from_page(page, engine).unwrap()
231 } else {
232 CanvasEditor::from_text_outlines(vec![outline], BTreeMap::new(), date).unwrap()
233 };
234 let layouts = |editor: &CanvasEditor| {
235 editor
236 .object_layouts()
237 .map(|(id, layout)| (id, layout.clone()))
238 .collect::<Vec<_>>()
239 };
240 for step in input.chunks_exact(8).take(32) {
241 let width = editor.active_outline().wrap_width();
242 if step[6] & 128 != 0 {
243 editor.place_caret(engine, position, width).unwrap();
244 }
245 let mut positions = Vec::new();
246 let mut source = String::new();
247 for (paragraph, text) in editor.active_outline().document().paragraphs().enumerate() {
248 if paragraph != 0 {
249 source.push('\n');
250 }
251 let base = source.len();
252 let mut offset = 0;
253 for (byte, character) in text.text().char_indices() {
254 positions.push((TextPosition { paragraph, offset }, base + byte));
255 offset += character.len_utf16() as u32;
256 }
257 positions.push((TextPosition { paragraph, offset }, base + text.text().len()));
258 source.push_str(text.text());
259 }
260 let (anchor, a) = positions[usize::from(step[0]) % positions.len()];
261 let (focus, b) = positions[usize::from(step[1]) % positions.len()];
262 editor.select([anchor, focus].into()).unwrap();
263 let before = editor.active_outline().document().clone();
264 let layouts_before = layouts(&editor);
265 let selection = editor.selection();
266 let text = [
267 "",
268 "a",
269 "e\u{301}",
270 "👩🏽‍💻",
271 "\n",
272 "left\nright\u{000b}soft",
273 "العربية",
274 "日本",
275 ][usize::from(step[2]) % 8];
276 let text = if text.is_empty() && editor.caret_outline().is_some() {
277 "x"
278 } else {
279 text
280 };
281 if step[3] & 1 != 0 {
282 let end = text.encode_utf16().count() as u32;
283 editor.compose(engine, text.into(), end..end).unwrap();
284 assert!(editor.cancel_composition(engine).unwrap());
285 assert_eq!(editor.active_outline().document(), &before);
286 assert_eq!(editor.selection(), selection);
287 assert_eq!(layouts(&editor), layouts_before);
288 }
289 let format = Format {
290 font_size: Some(10.0 + f32::from(step[4] % 8)),
291 bold: Some(step[5] & 1 != 0),
292 italic: Some(step[5] & 2 != 0),
293 ..Format::default()
294 };
295 editor
296 .replace(
297 engine,
298 text.split('\n')
299 .map(|part| Paragraph::new(part.into(), format.clone()))
300 .collect(),
301 )
302 .unwrap();
303 source.replace_range(a.min(b)..a.max(b), text);
304 assert_eq!(
305 editor
306 .active_outline()
307 .document()
308 .paragraphs()
309 .map(|p| p.text())
310 .collect::<Vec<_>>()
311 .join("\n"),
312 source
313 );
314 let after = editor.active_outline().document().clone();
315 let layouts_after = layouts(&editor);
316 let after_selection = editor.selection();
317 assert!(editor.undo(engine).unwrap());
318 assert_eq!(editor.active_outline().document(), &before);
319 assert_eq!(editor.selection(), selection);
320 assert_eq!(layouts(&editor), layouts_before);
321 assert!(editor.redo(engine).unwrap());
322 assert_eq!(editor.active_outline().document(), &after);
323 assert_eq!(editor.selection(), after_selection);
324 assert_eq!(layouts(&editor), layouts_after);
325 if step[5] & 128 != 0
326 && editor.caret_outline().is_none()
327 && !editor.active_outline().title
328 {
329 let outlines = editor
330 .outlines()
331 .iter()
332 .map(|outline| {
333 (
334 outline.id,
335 outline.layout().clone(),
336 outline.document().clone(),
337 )
338 })
339 .collect::<Vec<_>>();
340 let id = editor.active_outline().id;
341 editor.select_all().unwrap();
342 let selection = editor.selection();
343 editor.delete(engine, true).unwrap();
344 assert!(editor.outlines().iter().all(|outline| outline.id != id));
345 assert_eq!(editor.caret_outline().unwrap().id, id);
346 editor.place_caret(engine, [50.0, 90.0], width).unwrap();
347 for _ in 0..2 {
348 assert!(editor.undo(engine).unwrap());
349 assert_eq!(editor.selection(), selection);
350 assert_eq!(
351 editor
352 .outlines()
353 .iter()
354 .map(|outline| {
355 (
356 outline.id,
357 outline.layout().clone(),
358 outline.document().clone(),
359 )
360 })
361 .collect::<Vec<_>>(),
362 outlines
363 );
364 assert!(editor.redo(engine).unwrap());
365 assert_eq!(editor.caret_outline().unwrap().id, id);
366 }
367 assert!(editor.undo(engine).unwrap());
368 }
369 if step[6] & 1 != 0 && !editor.active_outline().title {
370 let width = [1.0, 10.0, 72.0, 240.0][usize::from(step[7]) % 4];
371 let before = editor.active_outline().layout().clone();
372 let document = editor.active_outline().document().clone();
373 let selection = editor.selection();
374 let preview = editor.preview_resize(engine, width).unwrap();
375 assert_eq!(editor.active_outline().layout(), &before);
376 assert_eq!(editor.active_outline().document(), &document);
377 editor.resize(engine, width).unwrap();
378 assert_eq!(editor.active_outline().layout(), preview.layout());
379 if before != *preview.layout() && editor.caret_outline().is_none() {
380 editor.undo(engine).unwrap();
381 assert_eq!(editor.active_outline().layout(), &before);
382 assert_eq!(editor.active_outline().document(), &document);
383 assert_eq!(editor.selection(), selection);
384 editor.redo(engine).unwrap();
385 assert_eq!(editor.active_outline().layout(), preview.layout());
386 }
387 }
388 if step[6] & 2 != 0 {
389 let before = editor
390 .outlines()
391 .iter()
392 .map(|outline| (outline.id, outline.document().clone()))
393 .collect::<Vec<_>>();
394 let movement = [
395 Movement::Left,
396 Movement::Right,
397 Movement::Up,
398 Movement::Down,
399 Movement::WordLeft,
400 Movement::WordRight,
401 Movement::LineStart,
402 Movement::LineEnd,
403 Movement::ParagraphStart,
404 Movement::ParagraphEnd,
405 Movement::DocumentStart,
406 Movement::DocumentEnd,
407 ][usize::from(step[7] >> 2) % 12];
408 editor
409 .move_selection(engine, movement, step[6] & 4 != 0)
410 .unwrap();
411 assert_eq!(
412 editor
413 .outlines()
414 .iter()
415 .map(|outline| (outline.id, outline.document().clone()))
416 .collect::<Vec<_>>(),
417 before
418 );
419 let caret = editor.caret(1.0).unwrap();
420 for unit in [
421 SelectionUnit::Grapheme,
422 SelectionUnit::Word,
423 SelectionUnit::Paragraph,
424 ] {
425 let selection = editor
426 .selection_at(caret.x0 as f32, ((caret.y0 + caret.y1) * 0.5) as f32, unit)
427 .unwrap();
428 editor.select(selection).unwrap();
429 }
430 }
431 if step[5] & 32 != 0 {
432 let id = editor.active_outline().id;
433 let point = editor
434 .outlines()
435 .iter()
436 .find(|outline| outline.id == id)
437 .map(|outline| {
438 [
439 outline.bounds().width() as f32 * 0.5,
440 outline.bounds().height() as f32
441 + [1.0, 20.0, 26.0, 28.0][usize::from(step[0]) % 4],
442 ]
443 })
444 .unwrap_or([0.0; 2]);
445 let stored = editor
446 .outlines()
447 .iter()
448 .map(|outline| {
449 (
450 outline.id,
451 outline.document().clone(),
452 outline.layout().clone(),
453 )
454 })
455 .collect::<Vec<_>>();
456 let before = editor.active_outline().document().clone();
457 let selection = editor.selection();
458 let extended = editor.select_below(engine, id, point).unwrap();
459 assert_eq!(
460 editor
461 .outlines()
462 .iter()
463 .map(|outline| (
464 outline.id,
465 outline.document().clone(),
466 outline.layout().clone()
467 ))
468 .collect::<Vec<_>>(),
469 stored
470 );
471 if !extended {
472 assert_eq!(editor.active_outline().document(), &before);
473 assert_eq!(editor.selection(), selection);
474 }
475 }
476 if editor
477 .outlines()
478 .iter()
479 .find(|outline| outline.id == editor.active_outline().id)
480 .is_some_and(|outline| outline.document() != editor.active_outline().document())
481 {
482 let pending = editor.active_outline().document().clone();
483 let stored = editor
484 .outlines()
485 .iter()
486 .map(|outline| (outline.id, outline.document().clone()))
487 .collect::<Vec<_>>();
488 editor
489 .move_selection(engine, Movement::DocumentEnd, false)
490 .unwrap();
491 let selection = editor.selection();
492 let mut expected = pending
493 .paragraphs()
494 .map(|paragraph| paragraph.text())
495 .collect::<Vec<_>>()
496 .join("\n");
497 expected.push('x');
498 editor.insert(engine, "x").unwrap();
499 assert_eq!(
500 editor
501 .active_outline()
502 .document()
503 .paragraphs()
504 .map(|paragraph| paragraph.text())
505 .collect::<Vec<_>>()
506 .join("\n"),
507 expected
508 );
509 assert!(editor.undo(engine).unwrap());
510 assert_eq!(editor.active_outline().document(), &pending);
511 assert_eq!(editor.selection(), selection);
512 assert_eq!(
513 editor
514 .outlines()
515 .iter()
516 .map(|outline| (outline.id, outline.document().clone()))
517 .collect::<Vec<_>>(),
518 stored
519 );
520 editor.place_caret(engine, [0.0; 2], width).unwrap();
521 }
522 if step[6] & 8 != 0 && editor.caret_outline().is_none() {
523 let before = editor.active_outline().document().clone();
524 let selection = editor.selection();
525 match editor.indent(engine, step[6] & 16 != 0) {
526 Ok(true) => {
527 let indented = editor.active_outline().document().clone();
528 assert!(editor.undo(engine).unwrap());
529 assert_eq!(editor.active_outline().document(), &before);
530 assert_eq!(editor.selection(), selection);
531 assert!(editor.redo(engine).unwrap());
532 assert_eq!(editor.active_outline().document(), &indented);
533 assert!(editor.undo(engine).unwrap());
534 }
535 Ok(false) | Err(_) => {
536 assert_eq!(editor.active_outline().document(), &before);
537 assert_eq!(editor.selection(), selection);
538 }
539 }
540 }
541 if step[6] & 32 != 0 {
542 let provisional = editor.caret_outline().is_some();
543 let stored = editor
544 .outlines()
545 .iter()
546 .map(|outline| (outline.id, outline.document().clone()))
547 .collect::<Vec<_>>();
548 let before = editor.active_outline().document().clone();
549 let selection = editor.selection();
550 let movement = [
551 Movement::WordLeft,
552 Movement::WordRight,
553 Movement::LineStart,
554 Movement::LineEnd,
555 ][usize::from(step[7]) % 4];
556 if editor.delete_to(engine, movement).unwrap() {
557 if provisional && editor.active_outline().is_empty() {
558 assert_eq!(
559 editor
560 .outlines()
561 .iter()
562 .map(|outline| (outline.id, outline.document().clone()))
563 .collect::<Vec<_>>(),
564 stored
565 );
566 } else {
567 assert!(editor.undo(engine).unwrap());
568 assert_eq!(editor.active_outline().document(), &before);
569 assert_eq!(editor.selection(), selection);
570 }
571 } else {
572 assert_eq!(editor.active_outline().document(), &before);
573 assert_eq!(editor.selection(), selection);
574 }
575 }
576 if step[5] & 64 != 0
577 && let Some(date) = editor.date()
578 {
579 let before = date.source().paragraphs.clone();
580 let timestamp = date.timestamp();
581 let selection = editor.selection();
582 let updated = timestamp.wrapping_add(1 + u64::from(step[0]));
583 assert!(
584 editor
585 .change_date(engine, updated, [text.into(), "7:15 AM".into()])
586 .unwrap()
587 );
588 let after = editor.date().unwrap().source().paragraphs.clone();
589 assert_eq!(editor.selection(), selection);
590 assert!(editor.undo(engine).unwrap());
591 assert_eq!(editor.date().unwrap().source().paragraphs, before);
592 assert_eq!(editor.date().unwrap().timestamp(), timestamp);
593 assert!(editor.redo(engine).unwrap());
594 assert_eq!(editor.date().unwrap().source().paragraphs, after);
595 assert_eq!(editor.date().unwrap().timestamp(), updated);
596 let date = editor.date().unwrap();
597 let rebuilt =
598 PageDate::new(updated, date.source().clone(), engine, &BTreeMap::new())
599 .unwrap();
600 assert_eq!(date.layout().size, rebuilt.layout().size);
601 }
602 let rebuilt = TextOutline::from_outline(
603 engine,
604 &editor.active_outline().snapshot(),
605 &BTreeMap::new(),
606 )
607 .unwrap();
608 let geometry = |outline: &TextOutline| {
609 outline
610 .layouts()
611 .map(|(index, paragraph)| {
612 (
613 index,
614 paragraph.origin,
615 paragraph
616 .text
617 .lines()
618 .map(|(line, bounds)| {
619 (
620 bounds.source.clone(),
621 bounds.top,
622 bounds.baseline,
623 bounds.height,
624 line.metrics().advance,
625 )
626 })
627 .collect::<Vec<_>>(),
628 )
629 })
630 .collect::<Vec<_>>()
631 };
632 assert_eq!(geometry(editor.active_outline()), geometry(&rebuilt));
633 assert_eq!(editor.active_outline().bounds(), rebuilt.bounds());
634 let caret = editor.caret(1.0).unwrap();
635 assert!(
636 [caret.x0, caret.y0, caret.x1, caret.y1]
637 .iter()
638 .all(|v| v.is_finite())
639 );
640 }
641 });
642});