| 1 | //! The reading view over OneNote-written pages: what it offers, the order it reads, and that |
| 2 | //! the reflowed page keeps every paragraph and object while only the shown copy moves. |
| 3 | |
| 4 | use canvas::{ |
| 5 | layout::TextEngine, |
| 6 | reading::{self, Kept, Kind, Refusal, Verdict}, |
| 7 | }; |
| 8 | use onestore::{ |
| 9 | Arena, ExGuid, Section, |
| 10 | page::{Page, PageObject}, |
| 11 | }; |
| 12 | use std::collections::BTreeSet; |
| 13 | |
| 14 | const COLUMN: f32 = 230.0; |
| 15 | |
| 16 | fn first_page(bytes: &[u8]) -> Page { |
| 17 | let arena = Arena::default(); |
| 18 | let mut section = Section::open(&arena, bytes.to_vec()).unwrap(); |
| 19 | let space = section.pages().unwrap()[0].0; |
| 20 | section.page(space).unwrap() |
| 21 | } |
| 22 | |
| 23 | fn paragraphs(page: &Page) -> BTreeSet<ExGuid> { |
| 24 | page.objects |
| 25 | .iter() |
| 26 | .filter_map(|object| match object { |
| 27 | PageObject::Outline(outline) => Some(outline.paragraphs.iter().map(|p| p.id)), |
| 28 | _ => None, |
| 29 | }) |
| 30 | .flatten() |
| 31 | .collect() |
| 32 | } |
| 33 | |
| 34 | #[test] |
| 35 | fn a_planning_page_reads_down_one_column() { |
| 36 | let page = first_page(include_bytes!( |
| 37 | "../../../corpus/notebook-management/native/page-color/Garden.one" |
| 38 | )); |
| 39 | let mut engine = TextEngine::default(); |
| 40 | let read = reading::read(&page, COLUMN, &mut engine).unwrap(); |
| 41 | assert_eq!( |
| 42 | read.verdict, |
| 43 | Verdict::Partial(vec![Kept::Asides, Kept::SideBySide, Kept::Wide]) |
| 44 | ); |
| 45 | let reflowed = read.page.unwrap(); |
| 46 | // Splitting around asides drops no paragraph; only spacing blank runs could go. |
| 47 | assert_eq!(paragraphs(&reflowed), paragraphs(&page)); |
| 48 | // Every block starts at the column's left edge or an aside's indent, top to bottom. |
| 49 | let mut tops = Vec::new(); |
| 50 | for block in &read.blocks { |
| 51 | let object = reflowed |
| 52 | .objects |
| 53 | .iter() |
| 54 | .find(|object| object.id() == block.members[0]) |
| 55 | .unwrap(); |
| 56 | let layout = object.layout(); |
| 57 | if block.kind == Kind::Text { |
| 58 | assert!(layout.max_width.unwrap() <= COLUMN); |
| 59 | } |
| 60 | tops.push(layout.y.unwrap()); |
| 61 | } |
| 62 | assert!(tops.windows(2).all(|pair| pair[0] < pair[1]), "{tops:?}"); |
| 63 | // The page itself is untouched. |
| 64 | let again = first_page(include_bytes!( |
| 65 | "../../../corpus/notebook-management/native/page-color/Garden.one" |
| 66 | )); |
| 67 | assert_eq!( |
| 68 | serde_json::to_string(&page).unwrap(), |
| 69 | serde_json::to_string(&again).unwrap() |
| 70 | ); |
| 71 | } |
| 72 | |
| 73 | #[test] |
| 74 | fn a_drawing_is_not_reflowed() { |
| 75 | let page = first_page(include_bytes!( |
| 76 | "../../../corpus/ink-edit/drawing/candidate/ink.one" |
| 77 | )); |
| 78 | let read = reading::read(&page, COLUMN, &mut TextEngine::default()).unwrap(); |
| 79 | assert_eq!(read.verdict, Verdict::Refused(Refusal::Drawn)); |
| 80 | assert!(read.page.is_none()); |
| 81 | } |
| 82 | |
| 83 | #[test] |
| 84 | fn a_page_as_narrow_as_the_column_shows_as_laid_out() { |
| 85 | let page = first_page(include_bytes!( |
| 86 | "../../../corpus/append/round-01/complex/notebook/synthetic.one" |
| 87 | )); |
| 88 | let read = reading::read(&page, 2000.0, &mut TextEngine::default()).unwrap(); |
| 89 | assert_eq!(read.verdict, Verdict::Fits); |
| 90 | } |